Reputation: 3082
I'm creating an app that visualises stars using a NASA API. The colour of a star comes back as a 0 to 1 value, with 0 being pure blue, and 1 being pure red. Essentially I need to set up a way to convert 0-1 values in javascript to a sliding HEX (or rgb) scale, progressing like this:
0: blue (9aafff)
.165: blue white (cad8ff)
.33: white (f7f7ff)
.495: yellow white (fcffd4)
.66: yellow (fff3a1)
.825: orange (ffa350)
1: red (fb6252)
Is this possible? I don't have any idea how to even begin to approach this. Cheers!
Upvotes: 2
Views: 2511
Reputation: 382092
The best would be to work in another color space than the RGB one. For example HSL.
Example:
var stones = [ // Your Data
{v:0, hex:'#9aafff'},
{v:.165, hex:'#cad8ff'},
{v:.33, hex:'#f7f7ff'},
{v:.495, hex:'#fcffd4'},
{v:.66, hex:'#fff3a1'},
{v:.825, hex:'#ffa350'},
{v:1, hex:'#fb6252'},
]
stones.forEach(function(s){
s.rgb = hexToRgb(s.hex);
s.hsl = rgbToHsl.apply(0, s.rgb);
});
function valueToRgbColor(val){
for (var i=1; i<stones.length; i++) {
if (val<=stones[i].v) {
var k = (val-stones[i-1].v)/(stones[i].v-stones[i-1].v),
hsl = interpolArrays(stones[i-1].hsl, stones[i].hsl, k);
return 'rgb('+hslToRgb.apply(0,hsl).map(function(v){ return v|0})+')';
}
}
throw "bad value";
}
/**
* Converts an RGB color value to HSL. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes r, g, and b are contained in the set [0, 255] and
* returns h, s, and l in the set [0, 1].
*
* @param Number r The red color value
* @param Number g The green color value
* @param Number b The blue color value
* @return Array The HSL representation
*/
function rgbToHsl(r, g, b){
r /= 255, g /= 255, b /= 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 [h, s, l];
}
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 255].
*
* @param Number h The hue
* @param Number s The saturation
* @param Number l The lightness
* @return Array The RGB representation
*/
function hslToRgb(h, s, l){
var r, g, b;
if(s == 0){
r = g = b = l; // achromatic
}else{
function hue2rgb(p, q, t){
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [r * 255, g * 255, b * 255];
}
function hexToRgb(hex) {
return /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
.slice(1).map(function(v){ return parseInt(v,16) });
}
function interpolArrays(a,b,k){
var c = a.slice();
for (var i=0;i<a.length;i++) c[i]+=(b[i]-a[i])*k;
return c;
}
var stones = [ // Your Data
{v:0, hex:'#9aafff'},
{v:.165, hex:'#cad8ff'},
{v:.33, hex:'#f7f7ff'},
{v:.495, hex:'#fcffd4'},
{v:.66, hex:'#fff3a1'},
{v:.825, hex:'#ffa350'},
{v:1, hex:'#fb6252'},
]
stones.forEach(function(s){
s.rgb = hexToRgb(s.hex);
s.hsl = rgbToHsl.apply(0, s.rgb);
});
function valueToRgbColor(val){
for (var i=1; i<stones.length; i++) {
if (val<=stones[i].v) {
var k = (val-stones[i-1].v)/(stones[i].v-stones[i-1].v),
hsl = interpolArrays(stones[i-1].hsl, stones[i].hsl, k);
return 'rgb('+hslToRgb.apply(0,hsl).map(function(v){ return v|0})+')';
}
}
throw "bad value";
}
for (var i=0; i<=1; i+=.03) {
var color = valueToRgbColor(i);
$('<div>').css({background:color}).text(i.toFixed(2)+" -> "+color).appendTo('body');
}
body {
background: #222;
}
div {
width:200px;
margin:auto;
color: #333;
padding: 2px;
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
For this example, I took the color space conversion functions here but there are easy to find once you know what to look for.
Note that modern browsers understand HSL colors (exemple: background: hsl(120,100%, 50%);
) so, if you're just building HTML, you don't have to embed all this code in your page, just precompute the color stops and interpolate on the HSL values directly.
Upvotes: 2
Reputation: 48247
Since you have a list of values, all pretty well-saturated and bright, you can probably interpolate in the current (RGB) space for this. It won't be quite as pretty as if you convert to HSL, but will work fine for the colors you have.
Since you don't have any weighting or curves in the data, going with a simple linear interpolation should work just fine. Something like:
var stops = [
[0, 154, 175, 255],
[0.165, 202, 216, 255],
[0.33, 247, 247, 255],
[0.495, 252, 255, 212],
[0.66, 255, 243, 161],
[0.825, 255, 163, 80],
[1, 251, 98, 82]
];
function convertColor(color) {
var c = Math.min(Math.max(color, 0), 1); // Clamp between 0 and 1
// Find the first stop below c
var startIndex = 0;
for (; stops[startIndex][0] < c && startIndex < stops.length; ++startIndex) {
// nop
}
var start = stops[startIndex];
console.log('using stop', startIndex, 'as start');
// Find the next stop (above c)
var stopIndex = startIndex + 1;
if (stopIndex >= stops.length) {
stopIndex = stops.length - 1;
}
var stop = stops[stopIndex];
console.log('using stop', stopIndex, 'as stop');
// Find the distance from start to c and start to stop
var range = stop[0] - start[0];
var diff = c - start[0];
// Convert diff into a ratio from start to stop
if (range > 0) {
diff /= range;
}
console.log('interpolating', c, 'between', stop[0], 'and', start[0], 'by', diff);
// Convert from RGB to HSL
var a = rgbToHsl(start[1], start[2], start[3]);
var b = rgbToHsl(stop[1], stop[2], stop[3]);
console.log('hsl stops', a, b);
// Interpolate between the two colors (start * diff + (stop * (1 - diff)))
var out = [0, 0, 0];
out[0] = a[0] * diff + (b[0] * (1 - diff));
out[1] = a[1] * diff + (b[1] * (1 - diff));
out[2] = a[2] * diff + (b[2] * (1 - diff));
console.log('interpolated', out);
// Convert back from HSL to RGB
var r = hslToRgb(out[0], out[1], out[2]);
r = r.map(function(rv) {
// Round each component of the output
return Math.round(rv);
});
return r;
}
// Set the divs
var divs = document.querySelectorAll('.star');
Array.prototype.forEach.call(divs, function(star) {
var color = convertColor(star.dataset.color);
var colorStr = 'rgb(' + color[0] + ',' + color[1] + ',' + color[2] + ')';
console.log('setting', star, 'to', colorStr);
star.style.backgroundColor = colorStr;
});
// HSL to RGB conversion from http://stackoverflow.com/a/30758827/129032
function rgbToHsl(r, g, b) {
r /= 255, g /= 255, b /= 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 [h, s, l];
}
function hslToRgb(h, s, l) {
var r, g, b;
if (s == 0) {
r = g = b = l; // achromatic
} else {
function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [r * 255, g * 255, b * 255];
}
.star {
width: 24px;
height: 24px;
display: inline-block;
box-shadow: 0px 0px 16px -2px rgba(0, 0, 0, 0.66);
}
<div class="star" data-color="0.0"></div>
<div class="star" data-color="0.05"></div>
<div class="star" data-color="0.1"></div>
<div class="star" data-color="0.15"></div>
<div class="star" data-color="0.2"></div>
<div class="star" data-color="0.25"></div>
<div class="star" data-color="0.3"></div>
<div class="star" data-color="0.35"></div>
<div class="star" data-color="0.4"></div>
<div class="star" data-color="0.45"></div>
<div class="star" data-color="0.5"></div>
<div class="star" data-color="0.55"></div>
<div class="star" data-color="0.6"></div>
<div class="star" data-color="0.65"></div>
<div class="star" data-color="0.7"></div>
<div class="star" data-color="0.75"></div>
<div class="star" data-color="0.8"></div>
<div class="star" data-color="0.85"></div>
<div class="star" data-color="0.9"></div>
<div class="star" data-color="0.95"></div>
<div class="star" data-color="1.0"></div>
Upvotes: 0
Reputation: 1975
Here is one the solution in pure Javascript I just did. It process a linear interpolation between two colors.
/*
NASA color to RGB function
by Alexis Paques
It process a linear interpolation between two colors, here is the scheme:
0: blue
.165: blue white
.33: white
.495: yellow white
.66: yellow
.825: orange
1: red
*/
var blue = [0,0,255];
var bluewhite = [127,127,255];
var white = [255,255,255];
var yellowwhite = [255,255,127];
var yellow = [255,255,0];
var orange = [255,127,0];
var red = [255,0,0];
function color01toRGB(color01){
var RGB = [0,0,0];
var fromRGB = [0,0,0];
var toRGB = [0,0,0];
if(!color01)
return '#000000';
if(color01 > 1 || color01 < 0)
return '#000000';
if(color01 >= 0 && color01 <= 0.165 ){
fromRGB = blue;
toRGB = bluewhite;
}
else if(color01 > 0.165 && color01 <= 0.33 ){
fromRGB = bluewhite;
toRGB = white;
}
else if(color01 > 0.33 && color01 <= 0.495 ){
fromRGB = white;
toRGB = yellowwhite;
}
else if(color01 > 0.495 && color01 <= 0.66 ){
fromRGB = yellowwhite;
toRGB = yellow;
}
else if(color01 > 0.66 && color01 <= 0.825 ){
fromRGB = yellow;
toRGB = orange;
}
else if(color01 > 0.825 && color01 <= 1 ){
fromRGB = orange;
toRGB = red;
}
// 0.165
for (var i = RGB.length - 1; i >= 0; i--) {
RGB[i] = Math.round(fromRGB[i]*color01/0.165 + toRGB[i]*(1-color01/0.165)).toString(16);
};
return '#' + RGB.join('');
}
Upvotes: 0