Consider a list of strings you need to permanently assign a random color.
First you should turn the string into a hash.
var string = "string"
var hash = 0
for (var i = 0; i < string.length; i++) {
hash = string.charCodeAt(i) + ((hash << 5) - hash);
hash = hash & hash;
}
console.log(hash) // -891985903
string.charCodeAt(i)
returns the UTF-16 code for the character at indexi
- Bit operators work on 32 bits numbers. Any numeric operand in the operation is converted into a 32 bit number.
hash << 5
is equivalent tohash * Math.pow(2, 5)
(hash * 32
), except the bit operator<<
makes sure our result is a 32 bit number.hash & hash
again, makes sure we only return a 32 bit number.
Now we have something to play around with.
The simplest method is to turn our hash into an RGB string:
String.prototype.toRGB = function() {
var hash = 0;
if (this.length === 0) return hash;
for (var i = 0; i < this.length; i++) {
hash = this.charCodeAt(i) + ((hash << 5) - hash);
hash = hash & hash;
}
var rgb = [0, 0, 0];
for (var i = 0; i < 3; i++) {
var value = (hash >> (i * 8)) & 255;
rgb[i] = value;
}
return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
}
"string".toRGB() // rgb(17, 96, 213)
Or a hexadecimal string (fiddle)
String.prototype.toHex = function() {
var hash = 0;
if (this.length === 0) return hash;
for (var i = 0; i < this.length; i++) {
hash = this.charCodeAt(i) + ((hash << 5) - hash);
hash = hash & hash;
}
var color = '#';
for (var i = 0; i < 3; i++) {
var value = (hash >> (i * 8)) & 255;
color += ('00' + value.toString(16)).substr(-2);
}
return color;
}
"string".toHex() // #1160d5
The issue is this can potentially spit out any color value. Ideally we'd want to filter out values too similar to our background color, and some gray/bland colors.
What if we hand pick some colors, and assign each string one of those? (fiddle)
String.prototype.toColor = function() {
var colors = ["#e51c23", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#5677fc", "#03a9f4", "#00bcd4", "#009688", "#259b24", "#8bc34a", "#afb42b", "#ff9800", "#ff5722", "#795548", "#607d8b"]
var hash = 0;
if (this.length === 0) return hash;
for (var i = 0; i < this.length; i++) {
hash = this.charCodeAt(i) + ((hash << 5) - hash);
hash = hash & hash;
}
hash = ((hash % colors.length) + colors.length) % colors.length;
return colors[hash];
}
"string".toColor() // #e91e63
This method is better if we want to be very particular over what colors are allowed, but selecting a large number colors can get tedious.
How about we use the hash to pick a hue, then hardcode the intensity/lightness. (fiddle)
String.prototype.toHue = function() {
var hash = 0;
if (this.length === 0) return hash;
for (var i = 0; i < this.length; i++) {
hash = this.charCodeAt(i) + ((hash << 5) - hash);
hash = hash & hash;
}
return hash % 360;
}
"string".toHue() // -223
Note: This can result in a negative value. CSS's hsl()
handles values outside of 0..360
perfectly, but not everything does.
For the sake of engineering, lets expand this out a bit more to include range values. (fiddle)
String.prototype.toHSL = function(opts) {
var h, s, l;
opts = opts || {};
opts.hue = opts.hue || [0, 360];
opts.sat = opts.sat || [75, 100];
opts.lit = opts.lit || [40, 60];
var range = function(hash, min, max) {
var diff = max - min;
var x = ((hash % diff) + diff) % diff;
return x + min;
}
var hash = 0;
if (this.length === 0) return hash;
for (var i = 0; i < this.length; i++) {
hash = this.charCodeAt(i) + ((hash << 5) - hash);
hash = hash & hash;
}
h = range(hash, opts.hue[0], opts.hue[1]);
s = range(hash, opts.sat[0], opts.sat[1]);
l = range(hash, opts.lit[0], opts.lit[1]);
return `hsl(${h}, ${s}%, ${l}%)`;
}
"string".toHSL() // hsl(137, 97%, 57%)
"string".toHSL({
hue: [-45, 45],
sat: [75, 95],
lit: [45, 55]
}) // hsl(2, 92%, 52%)
Unfortunately, HSL's lightness doesn't tend to correspond well to how light or dark your eyes see colors.
The simple solution is to darken your yellows and cyans and lighten your blues.
The "right" solution is to use a color space that lets you specify luma, saturation, and hue. Something like http://vis4.net/blog/posts/avoid-equidistant-hsv-colors/