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%)