15

I have a table of action logs in my application. I want to assign rows a random colour based on the sessionID of that entry to help see patterns/grouped actions.

I have this so far:

console.log(stringToColorCode('mj3bPTCbIAVoNr93me1I'));

function stringToColorCode(str) {
    return '#'+ ('000000' + (Math.random()*0xFFFFFF<<0).toString(16)).slice(-6);
}

However I need to replace Math.random() with my string-integer, are there any techniques for converting a string to a random number that remains consistent with the random string?

8
  • Maybe I don't understand, but isn't 0xFFFFFF<<0 the same as 0xFFFFFF?
    – GolezTrol
    Commented Jul 24, 2013 at 22:06
  • 1
    @GolezTrol review operator precedence: the product happens first, so <<0 is essentially coercing to 32 bit integer
    – SheetJS
    Commented Jul 24, 2013 at 22:07
  • So it is an int cast disguised as a bitshift operation? Nice trick, but why not use Math.Floor or something like that?
    – GolezTrol
    Commented Jul 24, 2013 at 22:09
  • 1
    (parseInt(parseInt('mj3bPTCbIAVoNr93me1I', 36).toExponential().slice(2,-4)) & 0xFFFFFF).toString(16).toUpperCase(); // "32EF01"
    – Paul S.
    Commented Jul 24, 2013 at 22:13
  • 3
    See this answer: stackoverflow.com/questions/13785164/… He suggests using hash code of the string, return '#' + md5(string).slice(0, 6); Commented Jul 24, 2013 at 22:13

4 Answers 4

21

As requested, posting this as an awswer

var stringHexNumber = (                       // 1
    parseInt(                                 // 2
        parseInt('mj3bPTCbIAVoNr93me1I', 36)  // 3
            .toExponential()                  // 4
            .slice(2,-5)                      // 5
    , 10) & 0xFFFFFF                          // 6
).toString(16).toUpperCase(); // "32EF01"     // 7

So, what's going on?

  1. Things start on line 3 where 'mj3bPTCbIAVoNr93me1I' gets converted to an Integer, say x, by interpreting it as a base-36 number.
  2. Next, x is put into it's exponential form as a String on line 4. This is because with that many characters, x can be huge, this example is around 8e30, so convert it to a form that will be pretty standard.
  3. After this, line 5 trims off the beginning and end so you'll be left with just digits, e.g. '8.123e+30'.slice(2, -5) becomes '12'.
  4. Now we go back to line 2, where this gets converted back into an Integer again, this time in base 10.
  5. Then, line 6 truncates this number down to the range 0..16777215 (=== 0xFFFFFF) using a fast bitwise AND. This will also convert NaN to 0.
  6. Finally, line 7 converts this back into the upper case hex format we are used to seeing colours in, by writing the number in base 16 and changing the case.

If you want to use this, you may also want to ensure that the final number is 6 digits and stick a # on the front, which can be done via

'#' + ('000000' + stringHexNumber).slice(-6); // "#32EF01"
1
  • 3
    Couple of clarifications for us that did not immediately jump to this interesting conclusion :). I learned that base-36 has a name: the hexatridecimal system. It's a special case because it can be easily represented with 0-9A-Z, hence parseInt(sessionID,36) - assumes sessionID is alphanumeric. After the call to toExponential(), a slice(2,-5) is done. Starts at 2 to get past the first decimal point, ends at -5 from the end because the exponential could possibly have 5 characters - max is e+307, after that, toExponential() returns Infinity.
    – p e p
    Commented Jul 25, 2013 at 4:26
10
var color_codes = {};
function stringToColorCode(str) {
    return (str in color_codes) ? color_codes[str] : (color_codes[str] = '#'+ ('000000' + (Math.random()*0xFFFFFF<<0).toString(16)).slice(-6));
}
2
  • 7
    This works well but not on page reload. The page will look different every time it is loaded. The question didn't specify whether it needed to be consistent on load, so this may work for the questioner's needs. Commented Jul 24, 2013 at 22:15
  • 1
    ah storing the random hex in an object is such a simple but effective solution thank you!
    – Titan
    Commented Jul 24, 2013 at 22:28
2

Sweet question. What I did is create a global variable so you can consistently get the same color for a given input string. Once you have called stringToColorCode, it will only generate a random color for that string ONCE. You can rely on this to be consistent, so that if you call the function back to back with the same string, it will return the same random color. Only flaw I see is that it's possibly (but unlikely) that two different strings could be mapped to the same color, but that could be worked around if necessary.

Edit: When first answering, I didn't realize @Nirk had practically the same answer. To make this one a little more unique, use this which will give you consistent colors across page reloads.

console.log(stringToColorCode('mj3bPTCbIAVoNr93me1I'));

function stringToColorCode(str) {
    var sessionStoreKey = "myStringColors" + str;
    if (!sessionStorage[sessionStoreKey ]) {
        sessionStorage[sessionStoreKey] = Math.random()*0xFFFFFF<<0;       
    }

    var randomColor = sessionStorage[sessionStoreKey];

    return '#'+ randomColor;
}
5
  • You should not use an array for that purpose; btw @Nirk halready has the same solution :-)
    – Bergi
    Commented Jul 24, 2013 at 22:16
  • I understand that I (misleadingly) declared stringTorandomColorMap as [], but am I not simply setting the stringToRandomColorMap object's properties when I do that? Similar to this: stackoverflow.com/questions/7068968/…. Do you still think I'm improperly using an array? If so I'd like to hear why. Thanks!
    – p e p
    Commented Jul 24, 2013 at 22:23
  • Arrays are for numeric indices only. Read “Associative Arrays” Considered Harmful, and use an object instead (var stringToRandomColorMap = {})
    – Bergi
    Commented Jul 24, 2013 at 22:26
  • Yep, arrays are numeric-index only. I did not meant to initially declare it as an array. Indeed (quickly tried it in debugger), if I initially declare the object as an array, then later on I can access properties like .length. What's interesting then is that even if I set myArray["test"] = "something", myArray.length continues to be 0 even though myArray.test is obviously accessible. Thanks for pointing that out, it's not exactly what I initially would have assumed would happen, I figured myArray would cease to be an array but it did not.
    – p e p
    Commented Jul 24, 2013 at 22:33
  • 1
    What you did is called memoization, an optimization technique. You can read more about it here
    – Dogoku
    Commented Jul 24, 2013 at 22:34
-1

I resolved this on backing bean. This works for me in Java:

private void createDefaultColorFromName(final String name) {
    String md5 = "#" + md5(name).substring(0, 6);
    defaultColor = Color.decode(md5);
    int darkness = ((defaultColor.getRed() * 299) + (defaultColor.getGreen() * 587) + (defaultColor.getBlue() * 114)) / 1000;
    if (darkness > 125) {
        defaultColor = defaultColor.darker();
    }
}

I made the generated color a little darker for a white background...

2
  • This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post.
    – Santosh A
    Commented May 4, 2015 at 11:24
  • @SantoshA: I just wrote my solution for a similar problem! What's wrong with that??
    – Gatschet
    Commented Jul 12, 2016 at 5:55

Not the answer you're looking for? Browse other questions tagged or ask your own question.