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?

Titan
  • 5,567
  • 9
  • 55
  • 90
  • Maybe I don't understand, but isn't `0xFFFFFF<<0` the same as `0xFFFFFF`? – GolezTrol Jul 24 '13 at 22:06
  • 1
    @GolezTrol review [operator precedence](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FOperators%2FOperator_Precedence): the product happens first, so <<0 is essentially coercing to 32 bit integer – SheetJS Jul 24 '13 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 Jul 24 '13 at 22:09
  • 1
    `(parseInt(parseInt('mj3bPTCbIAVoNr93me1I', 36).toExponential().slice(2,-4)) & 0xFFFFFF).toString(16).toUpperCase(); // "32EF01"` – Paul S. Jul 24 '13 at 22:13
  • 3
    See this answer: http://stackoverflow.com/questions/13785164/js-need-a-css-color-code-based-on-a-string He suggests using hash code of the string, return '#' + md5(string).slice(0, 6); – Bumptious Q Bangwhistle Jul 24 '13 at 22:13
  • @GolezTrol You can also do `|0`, which is something people usually do (and I think asm.js also uses to infer types) – SheetJS Jul 24 '13 at 22:17
  • @PaulS.: Please post that as an answer (with a little explanation), it's brilliant! – Bergi Jul 24 '13 at 22:24
  • @Bergi posted answer with full (?) explanation – Paul S. Jul 25 '13 at 00:14

4 Answers4

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"
Community
  • 1
  • 1
Paul S.
  • 64,864
  • 9
  • 122
  • 138
  • 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 Jul 25 '13 at 04: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));
}
SheetJS
  • 22,470
  • 12
  • 65
  • 75
  • 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. – Bumptious Q Bangwhistle Jul 24 '13 at 22:15
  • 1
    ah storing the random hex in an object is such a simple but effective solution thank you! – Titan Jul 24 '13 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;
}
p e p
  • 6,593
  • 2
  • 23
  • 32
  • You should not use an array for that purpose; btw @Nirk halready has the same solution :-) – Bergi Jul 24 '13 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: http://stackoverflow.com/questions/7068968/getting-a-custom-objects-properties-by-string-var. Do you still think I'm improperly using an array? If so I'd like to hear why. Thanks! – p e p Jul 24 '13 at 22:23
  • Arrays are for numeric indices only. Read [“Associative Arrays” Considered Harmful](http://andrewdupont.net/2006/05/18/javascript-associative-arrays-considered-harmful/), and use an object instead (`var stringToRandomColorMap = {}`) – Bergi Jul 24 '13 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 Jul 24 '13 at 22:33
  • 1
    What you did is called `memoization`, an optimization technique. You can read more about it [here](http://addyosmani.com/blog/faster-javascript-memoization/) – Dogoku Jul 24 '13 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...

Gatschet
  • 1,337
  • 24
  • 38
  • 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 May 04 '15 at 11:24
  • @SantoshA: I just wrote my solution for a similar problem! What's wrong with that?? – Gatschet Jul 12 '16 at 05:55