What is the best way to pick random colors for a bar chart / histogram such that each color is different from the other.. and possibly in contrast
The most talked about way is
'#'+(Math.random()*0xFFFFFF<<0).toString(16);
but this can generate similar colors.. and sometimes distinguishing them might be a problem..
Example
I would generate colors using HSV (hue, saturation, value) instead of RGB. In HSV, the color is defined by the hue, ranging from 0-360. Thus, if you want e.g. 6 different colors, you can simply divide 360 by 5 (because we want to include 0) and get 72, so each color should increment with 72. Use a function like this one to convert the generated HSV color to RGB.
The following function returns an array of total different colors in RGB format. Note that the colors won't be "random" in this example, as they will always range from red to pink.
function randomColors(total)
{
var i = 360 / (total - 1); // distribute the colors evenly on the hue range
var r = []; // hold the generated colors
for (var x=0; x<total; x++)
{
r.push(hsvToRgb(i * x, 100, 100)); // you can also alternate the saturation and value for even more contrast between the colors
}
return r;
}
The best way is to convert from HSV values. You can divide the maximum value of "Hue" by the amount of colors you need and then increment by this result.
For improved contrast, you can also alternate between high and low values of lightness.
The existing answers which mention the Hue, Saturation, Value representation of colors are very elegant, are closer to how humans perceive color, and it is probably best to follow their advice. Also creating a long precalculated list of colors and choosing subsets of them as needed is fast and reliable.
However, here is some code that answers your question directly: it will generate random colors in RGB that are sufficiently different. There are two drawbacks to this technique that I can see. First, these colors are really random and could look kind of gross together, and second it might take a while for the code to stumble on colors that work, depending on how "far apart" you require the colors to be.
function hex2rgb(h) {
return [(h & (255 << 16)) >> 16, (h & (255 << 8)) >> 8, h & 255];
}
function distance(a, b) {
var d = [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
return Math.sqrt((d[0]*d[0]) + (d[1]*d[1]) + (d[2]*d[2]));
}
function freshColor(sofar, d) {
var n, ok;
while(true) {
ok = true;
n = Math.random()*0xFFFFFF<<0;
for(var c in sofar) {
if(distance(hex2rgb(sofar[c]), hex2rgb(n)) < d) {
ok = false;
break;
}
}
if(ok) { return n; }
}
}
function getColors(n, d) {
var a = [];
for(; n > 0; n--) {
a.push(freshColor(a, d));
}
return a;
}
The distance between colors is the Euclidean distance measured by the R, G, and B components. Thus the furthest that two colors (black and white) can be is about 441.67.
To use this code, call getColors where the first parameter is the number of colors, and the second is the minimum distance between any two of them. It will return an array of numerical RGB values.
I like using hsl values for specifying colour this way.
So
"color: hsl(" + getRandomArbitary(0, 360) + ", 50%, 50%)";
would give you random results, but that won't give you your distinct separations. So I'd base it on the i value of a loop. Something like,
for (var i = 0; i < whateverYourValue; i += 1) {
color = "color: hsl(" + i * 10 + ", 50%, 50%)";
// set your colour on whatever
}
obviously the above is indicative, and not valid code on it's own.
Want to know more on hsl? Check http://mothereffinghsl.com/ 'cause, you know, it's fun.
'#'+(Math.random()*0xFFFFFF<<0).toString(16);
Isn't the best method to use because it can generate values like #4567 which is missing two digits instead of generating #004567
It's better to pick each character individually like:
'#'+Math.floor(Math.random()*16).toString(16)+
Math.floor(Math.random()*16).toString(16)+
Math.floor(Math.random()*16).toString(16)+
Math.floor(Math.random()*16).toString(16)+
Math.floor(Math.random()*16).toString(16)+
Math.floor(Math.random()*16).toString(16);
But that can easily be reduced to picking three numbers since hex colours can be shortened. IE. #457 == #445577
Then if you want to decrease the number of posibilities and widen the gap between them you can use:
'#'+(5*Math.floor(Math.random()*4)).toString(16)+
(5*Math.floor(Math.random()*4)).toString(16)+
(5*Math.floor(Math.random()*4)).toString(16);
Which divides the number of choices for each color by 5, and then evens out the distribution equally.
I second what kbok and Harpyon say about working in HSV colorspace, and this little library makes it super easy to switch between RGB and HSV - and others.
Related
So if I use randomColor to generate a pretty random variation of "green", I get this for example:
This is a bit chaotic for my liking, I would instead like to get a list of green colors that have some sort of order or sorting to them. That look like they flow into each other. Something more like this:
The question is what principles are being used to sort the colors in that way. I would like to know how I could go about building a grid of colors that would look more pleasant than a random assortment of colors of a specific hue.
The code I used is simply this:
var randomColor = require('randomcolor')
var input = process.argv[2]
var colors = randomColor({
count: 20,
hue: input
})
If it makes a difference, I would like to be able to specify the number of rows and columns to divide the colors into as well. Any language or pseudocode would be fine to figure this out, but seeing it in JavaScript, especially if it involves bit manipulation like bitshifts, would be helpful but not necessary.
This doesn't quite seem to do what I want.
The greens on the image run across various hues they then go down in value (get less black) as you move down.
Your pretty color generator randomColor is predicated on an odd theory that colors that move through the golden ratio within the color space will be prettier, basically it just makes sure you tend to get linear steps. But, the code is CC0-Licensed and includes a number of useful things for your purposes. First they define what green is. And they define how to convert from RGB to HSL. I'd just alter that code to make linear steps through the correct hues and then in the row direction step along in value.
However, this is going to just create the same colors each time, so you might just define the list of green colors you like.
If you actually want to sort these colors, you can sort them by literally just sorting them. Use HexToHSB() in the code randomColor, then sort the values based on it's resulting hue value. This generally will have lighter and brighter colors but you can clearly see a less chaotic pattern throughout.
All color spaces tend to be 3 values so putting them in 2d tends to be a bit kludgy, so you might as well pick one or two metrics and use those.
function HexToHSB(hex) {
hex = hex.replace(/^#/, '');
hex = hex.length === 3 ? hex.replace(/(.)/g, '$1$1') : hex;
var red = parseInt(hex.substr(0, 2), 16) / 255,
green = parseInt(hex.substr(2, 2), 16) / 255,
blue = parseInt(hex.substr(4, 2), 16) / 255;
var cMax = Math.max(red, green, blue),
cMin = Math.min(red, green, blue),
delta = cMax - cMin,
saturation = cMax ? (delta / cMax) : 0;
switch (cMax) {
case 0:
return [0, 0, 0];
case cMin:
return [0, 0, cMax];
case red:
return [60 * (((green - blue) / delta) % 6) || 0, saturation, cMax];
case green:
return [60 * (((blue - red) / delta) + 2) || 0, saturation, cMax];
case blue:
return [60 * (((red - green) / delta) + 4) || 0, saturation, cMax];
}
}
var input = 'green'
var colors = randomColor({
count: 200,
hue: input
})
colors = colors.sort(function(a, b) {
var hsva = HexToHSB(a);
var hsvb = HexToHSB(b);
return hsva[0] - hsvb[0];
});
div = document.createElement("div");
document.body.appendChild(div);
colors.forEach(function(element) {
var d = document.createElement("button");
d.style.cssText = 'padding:5px; font-size:22px; width:50px; height:50px; background-color:' + element;
div.appendChild(d);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/randomcolor/0.5.4/randomColor.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
(Not intending this to be a full code inclusive answer)
Your greens in the second image, across the top run from a blue green to a yellow green. In practical terms this means the top left green is “0 red, maybe 255 green and 200 blue”. At the other side, in the top right, it’s maybe “200 red, 255 green, 0 blue”. (I would have made these more accurate by using a colour detector but I’m on a phone)
Descending down the rows they’re tending towards black by reducing the r g b values, so for the middle row they might go from rgb 0,127,100 to rgb 100,127,0
Algorithmically, though this might be easier to do by using hsv colours rather than rgb, as the columns across go from some higher hue to a lower hue and the rows downwards tend from a high value to a low value. This would probably be easier to implement than doing the rgb math as you just need to accept a hue that will vary from maybe h+40 to h-40 (from a full wheel of 360) and a matrix (and you probably will need to ask for rows and columns not just 20, otherwise how will you know to output 5x4 or 4x5)
Let me explain what I'm trying to do. I have data like
const dataByState = {
'Washington' : { ElectoralVotes : 12, RChance: 54, DChance: 46 },
'Oregon': { ElectoralVotes: 7, RChance: 51, DChance: 49 },
.
.
.
'Hawaii' : { ElectoralVotes: 4, RChance : 40, DChance: 60 }
};
where one of the above key-value pairs like
'Hawaii' : { ElectoralVotes: 4, RChance : 40, DChance: 60 }
means "In the state Hawaii, which has 4 electoral votes, there is a 40% chance of the Republican Candidate winning and a 60% chance of the Democrat candidate winning". What I'm ultimately trying to do is calculate the chance of each candidate winning the election. How this would be done in a perfect world is
Iterate through all 2^51 combinations of states
For each combination c, its combined electoral votes are greater than or equal to 270, add it to a collection C of collecions of states
For the Republican candidate, sum up the probabilities of winning each combination of states in C; call that value r. That's his/her chance of winning. The Democrat's chance is 1 - r.
But since I can't go through all 2^51, what I'm doing is choosing some N smaller than 51 and doing
Find a random 2^N combinations of states whose combined electoral votes sum to greater than or equal to 270; call this combination C.
For the Republican candidate, sum up the probabilities of winning each combination of states in C; call that value r. Multiply r by 2^(51-N). That's approximately his/her chance of winning. The Democrat's chance is 1 - r.
Anyhow, this doesn't seem to be working and I'm wondering whether my logic is wrong (I haven't taken statistics since college 3 years ago) or if I'm running into rounding errors. I'm getting a near 100% of the Republican winning (i.e. America being made great again) when I make the chance even in every state, which is wrong because it should calculate to about 50/50.
Code dump: https://jsfiddle.net/pqhnwek9/
The probability of a republican victory is
probRepVict = 0
for(combination in combinations) {
if(combination is republican victory) {
probRepVict += proability of combination
}
}
As you observe it is not feasible to calculate the entire sum. Hence, you choose some subset C to try to estimate this probability.
N = number of combination // 2^51
n = size of C
probRepVictEstimate = 0
for(combination in C) {
if(combination is republican victory) {
probRepVictEstimate += proability of combination
}
}
probRepVictEstimate *= N/n
In the last statement we assume that the probability of a victory scales linearly with the size of the subset.
I believe the logic goes wrong at several places in the script:
(1) When generating the random number you might not get a sufficiently many bits of randomness. For instance if there were 54 states you would be outside of the safe integer range. Some implementations might give you even less fewer bits of randomness (it did break for me in Node, which only give 32 bits). Thus I suggest adding a function
function getRandom() {
// Generate 32 random bits
var s = Math.floor(Math.random()*Math.pow(2, 32)).toString(2)
return new Array(32 - s.length + 1).join("0") + s
}
Replacing
const rand = Math.floor(Math.random() * Math.pow(2,states.length));
with const rand = getRandom() + getRandom();, and replace getCombo with
const getCombo = (i) => {
let combo = [];
for(var j = 0; j < states.length; ++j)
if(i[j] == "0")
combo.push(states[j]);
return combo;
}
(2) You need to count both wins and losses for the republican party to be able to estimate the probability. Thus you cannot add the complement of a combo (by the way, ~ is a bitwise operations, hence convert the operand to a 32-bit integer, so your code does not work as intended). Hence your code should be simplified to:
...
if(!winningCombos.hasOwnProperty(rand)) {
const stateCombo = getCombo(rand);
if(hasSufficientVotes(stateCombo))
{
winningCombos[rand] = stateCombo;
++wins;
}
++count;
}
...
(3) You should scale repubChanceSum by N/n, where N = Math.pow(2, 51) and n = limit. Note that limit should be considerably greater than winningCombos.length.
With these modifications the code correctly predicts a ~50% probability. See this modified fiddle.
Let's hope we get a more optimistic outlook for the future with more realistic probabilities.
I have setup a basic getRandomColor function in javascript but I am having trouble figuring out how to limit the color spectrum that it uses. I want it to be more White & Grey colors. Is this possible?
My code is as follows:
function getRandomColor() {
var letters = "0123456789ABCDEF".split("");
var color = "#";
for (var i = 0; i < 6; i++ ) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
Go to an online color picker like this one, and look through a bunch of grey/white/black colors. On the webpage I linked to, you can do this by clicking to the verrrry left of the color box.
Look and see if you can find a pattern in the ratio between red/blue/green in the colors you want. Let me know if you find the pattern.
Edit
Alright, I'll let the cat out of the bag like the other answer has already done. When we have a 6-digit hex code, we have 2 digits for red, 2 digits for blue, and 2 digits for green.
# 80 80 80
^ ^ ^
red amount | |-----green amount
|---------------- blue amount
Numbers with a high red amount are very red, (like #990000 or #ff0000) numbers with a high blue amount are very blue (#00ff00), etc...
If the amount of red, green, and blue are all the same, then the color will look grey. So, all of the following colors will be grey-ish
#757575
#343434
#a3a3a3
Because I copied the same 2 numbers over and over. In the first one, the red amount is the same as the blue amount is the same as the green amount, which is 75.
Now how do you get this into your code? Generate 2 random letters to get the first color, and then add that to the end of your string 2 more times.
Edit 2
Let's talk about the code in "The Low Flying Pelican"'s answer really quick.
The first line will construct an array with letters from "A" to "F" in it. This means that our resulting color will only have letters in its hex code from "A" to "F". If we want other digits, we need to add them to the start of this string. Because we are picking numbers randomly, the order of digits in this string does not matter.
The 4th and 5th lines (var digit1 = Math.floor(Math.random() * 6);) pick an INDEX into this array. Pretty much it works like this. If our array has the elements A, B, C, D, E, F, then the 0th one is A, the 1st one is B, and so on. This means that if we do
letters[0]
We will get A, and if we do
letters[1]
We will get B.
We want to do
letters[<some random number>]
Which gives us a surprise letter between A and F. We do this twice to get two surprise letters, like EB or CC. In the given code it works like this:
//Pick 2 random numbers between 0 and 5.
//Math.floor() rounds the random numbers down, so instead of
//getting 3.42123, we just get 3. This is necessary because
//we can't take the 3.4th element of our array!
var digit1 = Math.floor(Math.random() * 6);
var digit2 = Math.floor(Math.random() * 6);
Later you see this code
letters[digit1]
which fetches the letter associated with the random number named "digit1"
If we are only picking random letters between A and F, we can't get the full range of greyscale. For that we need to pick random numbers from 0 to F. To do this, we need to add "0123...89" to the start of the array in the given code, AND we need to change the code so it picks a number between 0 and 15, instead of 0 and 5.
White/Gray colors do have all components the same, so,
function getRandomColor() {
var letters = "ABCDEF".split("");
var color = "#";
var digit1 = Math.floor(Math.random() * 6);
var digit2 = Math.floor(Math.random() * 6);
for (var i = 0; i < 3; i++ ) {
color += letters[digit1];
color += letters[digit2];
}
return color;
}
change to use only ABCDEF values so that it would be more towards white and gray, also to make sure all 3 components are the same
I have a chart that displays the frequency of names in the population over time, just one name at a time that the user selects. For, say, "John", the maximum value is 0.08 (8% of all names), so it's no problem to display the percents on the y axis. For the name "Stevens," meanwhile, the peak value is 0.000014984.
I don't want to have a 60px left margin if I can avoid it, so I'd prefer for these small numbers to be in scientific notation.
Right now I have a hacky solution:
y.tickFormat(function(d) {
if (d == 0) {
return 0;
}
if (d > 0.001) { // arbitrary cutoff
return d3.format(",.1%")(d);
}
return d3.format(".2e")(d * 100) + "%";
});
Is there a cleaner way to do this, where numbers from 1-99, for example, don't get "e0" appended?
This problem popped up when I looked at an almost perfect bit of jQuery code called Jar Two Wheel Color Picker:
http://www.jar2.net/projects/jquery-wheelcolorpicker/demo
If you notice in their demo when you move your cursor over the wheel, the developer appears to be using an inverse color algorithm that's slightly imperfect. I mean, if you use it and paste in 8072ff, you end up with something you can't read in the textbox.
The fix in my mind was to change the algorithm so that it shows black when over lighter colors, and white when over darker colors.
I looked into the source and found where it's updating the color of the textbox field. There, I'm able to get the R, G, and B color values from 0 to 255.
In Javascript and jQuery, knowing the R, G, and B values of a background color, how can I switch the foreground color to either white or black so that it is readable?
The conversion to hsl makes finding contrasting colors easy-
but it takes a bit to convert between rgb and hsl.
This works well enough for most cases- but it won't replace your eye.
function bW(r){
//r must be an rgb color array of 3 integers between 0 and 255.
var contrast= function(B, F){
var abs= Math.abs,
BG= (B[0]*299 + B[1]*587 + B[2]*114)/1000,
FG= (F[0]*299 + F[1]*587 + F[2]*114)/1000,
bright= Math.round(Math.abs(BG - FG)),
diff= abs(B[0]-F[0])+abs(B[1]-F[1])+abs(B[2]-F[2]);
return [bright, diff];
}
var c, w= [255, 255, 255], b= [0, 0, 0];
if(r[1]> 200 && (r[0]+r[2])<50) c= b;
else{
var bc= contrast(b, r);
var wc= contrast(w, r);
if((bc[0]*4+bc[1])> (wc[0]*4+wc[1])) c= b;
else if((wc[0]*4+wc[1])> (bc[0]*4+bc[1])) c= w;
else c= (bc[0]< wc[0])? w:b;
}
return 'rgb('+c.join(',')+')';
}
Given the RGB/HSV conversion functions from Michael Jackson and this bit of HTML to play with:
<input type="text" id="pancakes">
<button id="margie">go</button>
Then something like this might be a decent starting point:
$('#margie').click(function() {
var bg = $('#pancakes').val();
var rgb = bg.match(/../g);
for(var i = 0; i < 3; ++i)
rgb[i] = parseInt(rgb[i], 16);
var hsv = rgbToHsv(rgb[0], rgb[1], rgb[2]);
var fg = 'ffffff';
if(hsv[2] > 0.5)
fg = '000000';
$('#pancakes').css({
color: '#' + fg,
background: '#' + bg
});
});
Demo: http://jsfiddle.net/ambiguous/xyA4a/
You might want to play with the hsv[2] > 0.5 and possibly look at the hue and saturation as well but just a simple value check should get you started. You might want to play with HSL rather than HSV and see which one works better for you.