# How to find a maximum set of well separated color-gradients black-to-color?

I have a question similar to, but hopefully sufficiently different from, this question here: 'Where can I find a large palette / set of contrasting colors for coloring many datasets on a plot?'

I have the following need: I want to display n different things in a single image with a gradient from black-to-color for each, and these things should be distinguishable from each other as best as possible. (For the human eye and on-screen-display.)

This clearly works well for n=3 with just red, green and blue channel, but I need to extend that concept to an as-high-as-possible number n. Is there a reference or publication that deals with that problem?

To illustrate:
I can start out with the 24 well-separated lighter colors scheme as described in this answer or publication:

But while the 24 displayed colors are nicely discernible, their black-to-color gradients are less so:

That color scheme is actually not too bad, as one can discern several of those colors to some rather dark shades, but I was wondering if somebody has analyzed the problem more systematically and came up with an optimal solution or algorithm.

Note, that the different colors may appear at any brightness-level and need to remain discernible. i.e. a "dark" variant of color #1 must also be different from a "light" variant of color #2.

As a counter-example: Just naively splitting the HUE value into n equidistant parts obviously does a bad job:

• No, there really is no way to do this. Thing is if you find it then you will be awarded a PhD for the explanation. You will have to sacrafice colors. 24 distinctive colors is allready a really big feat. Thing is human color vision is good but it does not really distinguish things in isolation. The problem is that inorder for you to use 24 colors you need to allready use the darkening axis in a way that reduces your space too much. Commented Sep 8, 2022 at 19:36
• You could for example use chroma.js for such a script. It's easy enough, but exactly what to calculate? You are talking about evaluating gradients separately. Then white would be the best base color is my guess, because you'll get the largest distance between colors in the gradient. But you also have to compare all the individual colors from all the gradients to find out if some of them are too close to each other. So you would have to test a set of colors and how to select them? Go through billions of combinations? Perhaps. Commented Sep 8, 2022 at 21:15
• I made this script for fun. You specify a set of colors and how many steps the gradients have. The script finds the average DeltaE between all pairs of colors in each gradient and calculates the average. Not surprisingly, it simply finds that the lighter the base color, the more distinguishable the steps are. Commented Sep 8, 2022 at 22:15
• The next step would be to compare all pairs of colors across gradients to find out how good the set is overall. But I don't think the method is sane though. You'll have to find out how to choose palettes to check. A gradient with high average Delta E will hide if another gradient in the set has a intolerable low average Delta E, so you would have to find a way to account for that. A palette could give a good result, but still have a few color pairs which is almost indiscernible. I think it's an evil rabbit hole to venture into. Commented Sep 8, 2022 at 22:20
• There's a recent study: "Rainbows are fantastic," explains lead author Fabio Crameri, "but in the context of displaying scientific, technical, medical or similar such data, it needs to be stopped." This is because the properties of the colors, and the way that the human eye understands them can lead to distortion. More at phys.org/news/2020-10-colours-science.html
– Stan
Commented Sep 8, 2022 at 23:18

OK the real answer is don't do it. Its not really possible to do this with very many colors. Finding singular colors that work is a significantly challenging thing, what your asking is even more challenging.

But it is true that there are tools for singular color clusters. You would need to change the way the clustering tools for example here works (theory explanation here and moderately mature code available here). So if you want to try go for it.

But here is my reasoning why it don't work very well. Lets work on simpler but similar case. image i want to pick distinct points in a 2D space and i want those points to be distinct form each other and as high in the space as possible. Then i would be actually trying to pack circles of some kind.

Image 1: This is what choosing individual distinct colors is conceptually doing. Anything closer than the circle can not be distinguished.

But the problem here is that a gradient is not a point but instead a line. So conceptually in this case points are lines. Staggering lines don't work so well as you get collision with what you have below.

Image 2: This is what choosing gradients to black would look like. Pink areas are problems.

So to be generous to the 3d space you probably can get maybe up to 2-3 levels of staggered points. This then means assuming a 24 colors would fit (a bit more can fit) would predict somewhere around 8-12 colors possible as gradients. The real situation is both better and worse as the color space is tapering towards the bottom but ill leave that to posterity to explain.

Off course i will readily admit this does not prove it can not be done.

Still seems insane expecting a human able to read a 26 dimension image. Just tell your client to think about alternatives. Or at least charge your clients for the research hundreds if not thousands a day with no expectation of success.

But YMMV 6-10 colors probably ok. Hard to say how well would work for color impairment so accessibility would be a serious issue.

• Thanks for explaining your argument with images and more details. I do now better understand. The mental image you have in mind, however, is a bit flawed as I'm not searching for a 'perfect' situation ( "no overlaps" ) but for an "as good as possible" one (minimizing the overlaps in regions that are most critical.) In practice, one won't have just a single color of one gradient (i.e. one intensity) in a region, but a region colored with varying intensities of that color. this will considerably improve the perception. Still "not all colors work equally well" - so I seek the "best set". Commented Sep 9, 2022 at 10:28
• But yes, putting 24+ colors in one map is most likely overkill for all situations. <8 are easy. It's the 8-14 range that is the most probable real use-case. However, the problem is that many customers want to assign fixed colors to a set of items. (i.e. item1 is always red etc.) and the set is bigger than 20. So, ideally they want to assign n colors so that when one picks m<n items to map those are nicely distinguished. Commented Sep 9, 2022 at 10:31
• @BmyGuest im just telling you what the physics and mathematics for this thing is. Physics dont care what you want. If you dont care about overlap then there is no problem just pick whatever. Commented Sep 9, 2022 at 10:44
• @BmyGuest no, but if you want them to be distinguishable then they need to have a big enough separation so yes you either care or you dont. Commented Sep 9, 2022 at 11:13
• @BmyGuest thats fine. Im just telling you that there is very little middle ground here. You can easily give client n colors out of which 8 is suitable for graduation. Theres not really any good way to maximize the difference. Client can want impossible all they want, but your never going to get any money if your income relys on this. Commented Sep 9, 2022 at 11:22

After some fruitful discussions here and elsewhere I've set out to find such a color scheme myself.
This is all work in progress. I will update as I proceed, but remarks and comments are welcome.

# Description of methods

## Comparing two sRGB colors

To compare two colors on my screen I need to compute a distance measure.

I have the colors as `sRGB` values.

I am first converting them into `CIE XYZ` values using the sRGB->CIE XYZ equations.

Then I convert them further then into `CIE L*a*b*` values using the CIE XYZ -> CIE Lab equations.

In this format, I can compute the CIEDE2000 distance value of the two colors. The higher the value, the better distinguishable are the two colors.

I now want to compare two sRGB colors that each generate a black-to-color gradient. This gradient is computed in `nGrad` colors. Because too dark colors always will be indistinguishable, I define a darkness cutoff below I do no longer care. For this darkness cutoff I convert the `sRGB` values into `HSV` values and require a minimum V-value of `Vmin`.

When I now compare two colors that each generate a gradient, I compute the `CIEDE2000` distance from one color of gradient#1 to all other colors of gradient#2 which are above `Vmin` and note the smallest of these values, i.e. the least distinguishable pairing. I repeat this for the other colors of the gradient that are above `Vmin`. The overall distance between the two gradients is then defined by the minimum value of the individual values.

In essence, I define the distance between two gradients as the minimum-distance of any color-pairing for colors above `Vmin`.

## Sorting and filtering sets of black-to-color gradients

With the above, I have a way to measure color distances between gradients. In a color set I have `nCol` colors (each generating a gradient), so I compute the distances between all of them. When I say "compute the distance between two colors" I really mean I compute the distance between their respective gradients as described before.

I now sort the list of colors such that the first color is the one with the largest distance to its closest neighbor color. i.e. each color computes the distance to all other colors, and the smallest distance of these measurements is used for the sorting.

Next, I iteratively add the remaining colors, always picking the one which has the largest minimum-distance to the colors already chosen. In essence, I always pick the color which is the most distinguishable from the already chosen one.

On top of that, I define a `DeltaE2000 minimum` value. Any color which isn't at least that distant from its nearest partner gets completely removed.

# Testing

As a starting point, I create a somewhat random test set of similar and dissimilar colors that generate gradients. Grey squares indicate colors that fall below my `Vmin = 0.1` threshold and are ignored for evaluation:

Applying the method from above and using a filter threshold `DeltaE2000min = 10` gives me the remaining, sorted set below. As I've rotated the image counter-clockwise for easier display, the sorting is from bottom to top:

The result is pretty much as expected, giving me some confidence in the results. I can now apply the same methods to the Light-24 color set from my original posting.

Original:

After sorting and filtering:

Note, that only two colors where dropped but that the sorting now allows me to pick N<=22 colors with maximum discernibly (as defined above) by starting with the bottom most color and going upwards as required.

## What's next?

As I'm somewhat confident in the methods now, the real 'search for the scheme' can begin. I haven't completely made up my mind, but I think I will start with a somewhat randomized approach where I iteratively pick a random bright color, test it against the set, and add it if sufficiently different by the defined metric. When no more color can be found after some break-up criteria, I will sort-filter that list. But it was a long day with lots of new stuff learnt, so this project is a bit postponed for now.

# Update

I finally came around finishing this. I did two approaches: One starting with 0 colors and just randomly adding as described above, and two starting with a reasonably 'spread out' color scheme and just adding colors there.

In either case, not surprisingly too harsh restrictions based on "considered dark shades" or "minimum color difference) limit the number of successfully added colors dramatically. In the end, I settled on accepting colors with `HSV V >= 0.2` and a `CIEDE2000 distance of >= 5`. With theses settings, palettes with ~30 colors seem doable.

Below are the three "best" examples I came up so far. One based on the Light-24 scheme from above, the other based on a palette starting out with RGB fractions, the final one totally random. The schemes are again sorted such that the color distance between columns is biggest on the left and decreases to the right.

39 colors based on the Light-24 scheme

RGB values as CSV

``````39 colors based on the Light-24 scheme
NAME, R value, G value, B value, minimum distance to all previous colors (gradient)

Color # 1 , 000 , 181 , 247 , 99.99
Color # 2 , 246 , 249 , 038 , 43.43
Color # 3 , 255 , 000 , 146 , 40.61
Color # 4 , 034 , 255 , 167 , 26.28
Color # 5 , 106 , 118 , 252 , 22.47
Color # 6 , 228 , 143 , 114 , 21.33
Color # 7 , 247 , 255 , 228 , 15.79
Color # 8 , 214 , 038 , 255 , 13.97
Color # 9 , 078 , 255 , 000 , 13.39
Color #10 , 255 , 202 , 255 , 12.81
Color #11 , 013 , 249 , 255 , 12.13
Color #12 , 255 , 080 , 091 , 9.79
Color #13 , 182 , 142 , 000 , 8.95
Color #14 , 255 , 150 , 022 , 8.56
Color #15 , 232 , 255 , 121 , 8.09
Color #16 , 110 , 137 , 156 , 8.05
Color #17 , 255 , 109 , 224 , 7.87
Color #18 , 254 , 212 , 196 , 7.23
Color #19 , 188 , 113 , 150 , 6.67
Color #20 , 255 , 205 , 126 , 6.53
Color #21 , 254 , 000 , 206 , 6.33
Color #22 , 128 , 204 , 000 , 6.31
Color #23 , 000 , 255 , 217 , 6.19
Color #24 , 210 , 255 , 192 , 6.17
Color #25 , 168 , 255 , 109 , 6.16
Color #26 , 201 , 251 , 229 , 6.03
Color #27 , 220 , 088 , 125 , 5.93
Color #28 , 000 , 254 , 053 , 5.60
Color #29 , 149 , 240 , 255 , 5.59
Color #30 , 071 , 155 , 085 , 5.56
Color #31 , 255 , 098 , 061 , 5.52
Color #32 , 146 , 255 , 234 , 5.45
Color #33 , 255 , 171 , 187 , 5.28
Color #34 , 126 , 125 , 205 , 5.11
Color #35 , 238 , 166 , 251 , 5.09
Color #36 , 227 , 238 , 158 , 5.05
Color #37 , 255 , 234 , 149 , 5.03
Color #38 , 255 , 232 , 255 , 5.02
Color #39 , 138 , 255 , 197 , 5.01
``````

38 colors based on a scheme using RGB fractions.

``````38 colors based on a scheme using RGB fractions.
NAME, R value, G value, B value, minimum distance to all previous colors (gradient)
Color # 1 , 128 , 128 , 255 , 99.99
Color # 2 , 000 , 255 , 000 , 52.03
Color # 3 , 255 , 128 , 000 , 31.31
Color # 4 , 000 , 250 , 255 , 24.75
Color # 5 , 255 , 000 , 128 , 23.13
Color # 6 , 255 , 255 , 000 , 18.63
Color # 7 , 255 , 255 , 244 , 15.51
Color # 8 , 255 , 000 , 255 , 14.15
Color # 9 , 084 , 168 , 252 , 12.61
Color #10 , 255 , 000 , 000 , 12.56
Color #11 , 202 , 255 , 156 , 11.50
Color #12 , 128 , 000 , 255 , 10.34
Color #13 , 255 , 143 , 187 , 10.13
Color #14 , 081 , 255 , 194 , 8.73
Color #15 , 255 , 186 , 171 , 8.50
Color #16 , 255 , 206 , 137 , 8.23
Color #17 , 255 , 128 , 255 , 7.99
Color #18 , 174 , 255 , 246 , 7.30
Color #19 , 134 , 255 , 000 , 7.27
Color #20 , 000 , 000 , 255 , 6.93
Color #21 , 255 , 255 , 128 , 6.90
Color #22 , 000 , 128 , 255 , 6.77
Color #23 , 255 , 190 , 255 , 6.67
Color #24 , 000 , 255 , 104 , 6.61
Color #25 , 255 , 167 , 000 , 6.51
Color #26 , 255 , 230 , 255 , 6.46
Color #27 , 255 , 128 , 128 , 6.13
Color #28 , 255 , 142 , 096 , 5.87
Color #29 , 255 , 206 , 000 , 5.65
Color #30 , 204 , 255 , 000 , 5.56
Color #31 , 252 , 084 , 168 , 5.56
Color #32 , 168 , 084 , 252 , 5.53
Color #33 , 255 , 085 , 046 , 5.46
Color #34 , 255 , 252 , 195 , 5.23
Color #35 , 128 , 255 , 128 , 5.19
Color #36 , 204 , 255 , 209 , 5.17
Color #37 , 216 , 229 , 255 , 5.11
Color #38 , 252 , 168 , 084 , 5.09
``````

32 colors created by pure random choice

RGB values as CSV

``````32 colors created by pure random choice
NAME, R value, G value, B value, minimum distance to all previous colors (gradient)

Color # 1 , 255 , 255 , 000 , 99.99
Color # 2 , 255 , 113 , 255 , 47.77
Color # 3 , 000 , 255 , 255 , 31.95
Color # 4 , 255 , 072 , 026 , 23.92
Color # 5 , 000 , 255 , 069 , 20.28
Color # 6 , 251 , 245 , 255 , 17.57
Color # 7 , 255 , 150 , 000 , 14.69
Color # 8 , 199 , 255 , 146 , 10.95
Color # 9 , 255 , 137 , 176 , 10.68
Color #10 , 255 , 253 , 205 , 8.20
Color #11 , 164 , 255 , 000 , 8.10
Color #12 , 177 , 255 , 255 , 7.69
Color #13 , 255 , 168 , 161 , 7.38
Color #14 , 000 , 255 , 201 , 7.37
Color #15 , 255 , 198 , 113 , 6.79
Color #16 , 255 , 082 , 112 , 6.05
Color #17 , 255 , 210 , 180 , 5.99
Color #18 , 255 , 114 , 038 , 5.97
Color #19 , 255 , 088 , 185 , 5.95
Color #20 , 255 , 172 , 255 , 5.72
Color #21 , 090 , 255 , 000 , 5.68
Color #22 , 156 , 255 , 172 , 5.64
Color #23 , 202 , 255 , 217 , 5.56
Color #24 , 255 , 169 , 109 , 5.51
Color #25 , 184 , 225 , 255 , 5.50
Color #26 , 255 , 239 , 137 , 5.46
Color #27 , 136 , 255 , 223 , 5.34
Color #28 , 138 , 255 , 108 , 5.27
Color #29 , 255 , 222 , 255 , 5.22
Color #30 , 000 , 255 , 131 , 5.21
Color #31 , 255 , 182 , 000 , 5.17
Color #32 , 223 , 255 , 106 , 5.10
``````
• According to mathematical theory random is the the best strategy if you don't better. Also don't visually evaluate them as swatches next to each other humans are good at this particular thing scatter them around and see of you can pick which color belongs to which color Commented Sep 10, 2022 at 7:11