Search code examples
rcolorsrgbcolor-schemehsv

Randomly generate 3 distinct colors


I've looked at this one but does not help with the random part. Is there a better way to randomly generate 3 different colors so that the square, the circle, and the text stand out visually from one another in the code below. The colors have to be randomly generated and also distinct enough from one another. The current code works maybe only half the time

plot(0, type = "n", xlim = c(0,10), ylim = c(0,10),
    ann = FALSE, axes = FALSE, asp = 1)

cols = colorRampPalette(sample(2:9,2), alpha = TRUE)(8)

polygon(x = c(1,9,9,1), y = c(1,1,9,9), border = NA, col = cols[1])

symbols(x = 5, y = 5, circles = 4, inches = FALSE,
    add = TRUE, bg = cols[4], fg = NA)

text(x = 5, y = 5, labels = "Hi", col = cols[7], font = 2, cex = 3)

EXAMPLES

GOOD

enter image description here

POLYGON: "#FF00FFFF", CIRCLE: "#916DFFFF", TEXT: "#24DAFFFF"

BAD

enter image description here

POLYGON: "#00FFFFFF", CIRCLE: "#51E3E3FF", TEXT: "#A2C7C7FF"


Solution

  • My original answer, which uses the hcl color space, often generated color combinations that were hard to distinguish. This updated answer uses the Lab color space, which is scaled based on the perceptual distance between colors, so similar distances in Lab space should correspond to similar perceptual color differences. In Lab, L is luminance or brightness on a scale of 0 to 100. a represents green to red, and b represents blue to yellow, with both on a scale of -100 to 100.

    The code below generates two random values for a and b. If we think of these two values as representing a point in the ab plane, we generate two more colors with maximum perceptual distance from each other by rotating this point first by 120 degrees and then by 240 degrees. We then choose a single L value to give us three equally spaced colors.

    Below I've packaged this in a function to make it easy to generate several plots with random colors. I've also set a minimum absolute value for a and b so that we don't get colors that are too similar, and included an Lval argument for choosing the L value of the Lab colors.

    Based on a several runs, it looks like this approach performs much better than my original hcl version (although this may be due not only to the use of Lab space instead of hcl space, but also because I used only one dimension of hcl space but two dimensions of Lab space).

    library(colorspace)
    
    random.colors = function(Lval=80, ABmin=50) {
      
      # 120 deg rotation matrix
      aa = 2*pi/3
      rot = matrix(c(cos(aa), -sin(aa), sin(aa), cos(aa)), nrow=2, byrow=TRUE)
      
      # Generate random A and B points in LAB space
      x = runif(2, ABmin, 100) * sample(c(-1,1), 2,replace=TRUE) 
      
      # Create three equally spaced colors in Lab space and convert to RGB
      cols = LAB(cbind(rep(Lval,3), rbind(x, x %*% rot, x %*% rot %*% rot)))
      cols = rgb(convertColor(cols@coords, from="Lab", to="sRGB"))
      
      plot(0, type = "n", xlim = c(0,10), ylim = c(0,10),
           ann = FALSE, axes = FALSE, asp = 1)
      
      polygon(x = c(1,9,9,1), y = c(1,1,9,9), border = NA, col = cols[1])
      
      symbols(x = 5, y = 5, circles = 4, inches = FALSE,
              add = TRUE, bg = cols[2], fg = NA)
      
      text(x = 5, y = 5, labels = "Hi", col = cols[3], font = 2, cex = 3)
    }
    
    par(mfrow=c(3,3), mar=rep(0,4))
    replicate(9,random.colors())  
    

    enter image description here

    For simplicity, the example above constrains the a and b values to be a constant distance from the origin (in ab-space) and uses the same L value for all three colors. You could instead extend this method to use all three dimensions of the Lab space. In addition, instead of requiring a constant distance from the origin, you could pick the first color at random and then require that the next two colors be picked such that all three colors are maximally separated from each other in Lab space.

    Original Answer

    You could generate colors that are equally spaced in the hue dimension (that is, have the maximum possible hue separation from each other). For example:

    set.seed(60)
    cols = hcl(runif(1,0,359.99) + c(0,120,240), 100, 65)
    
    plot(0, type = "n", xlim = c(0,10), ylim = c(0,10),
         ann = FALSE, axes = FALSE, asp = 1)
    
    polygon(x = c(1,9,9,1), y = c(1,1,9,9), border = NA, col = cols[1])
    
    symbols(x = 5, y = 5, circles = 4, inches = FALSE,
            add = TRUE, bg = cols[2], fg = NA)
    
    text(x = 5, y = 5, labels = "Hi", col = cols[3], font = 2, cex = 3)
    

    enter image description here

    Here are nine more random draws. As you can see, there are some combinations that don't work too well. But perhaps you can play around with different ways of slicing up the color space to see if you can get something better.

    enter image description here