Bold Colors
Published 2010-11-02 @ 15:11
Tagged toys
I often create complex visualizations using graphviz and my graph
gem (I still mourn vcg–vastly superior, but unsupported). I often want to use color to help with the visualization, but picking colors that are distinct is hard.
GV by default supports X11 colors, which have a ton of useless gradients like antiquewhite[1-4], aquamarine[1-4], azure[1-4], etc. Further, many of these colors are simply unusable on a white background. Anything pale or yellow just seems to disappear. I decided to see what I could do to fix that.
There are 655 colors in the X11 color list (for GV, at least). The easiest thing to do is to strip all numbered colors. That brings drops me way down to 141 colors. Much more manageable, but many of those are pale weak colors that don’t hold up to a white background.
So at this point, the best thing that I could think of was to convert all the RGB values to HSV and strip out all the unsaturated (and all yellow) colors. I also wanted to spread out like colors as much as possible so that I get maximum contrast as I walk through and use the colors in a graph. With those parameters, I wind up with 47 stark spread out colors:
% ./colors.rb
47
%w[black brown mediumblue blueviolet orange magenta darkgreen
maroon violetred purple greenyellow deeppink midnightblue
firebrick darkturquoise mediumspringgreen chartreuse navy
lightseagreen chocolate lawngreen green indigo darkgoldenrod
darkviolet red springgreen saddlebrown mediumvioletred
goldenrod tomato cyan forestgreen darkorchid crimson coral
deepskyblue seagreen peru turquoise orangered dodgerblue sienna
limegreen royalblue darkorange blue]
Here is the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
def rgb2hsb(r, g, b) h = 0.0 min = [r, g, b].min max = [r, g, b].max delta = (max - min).to_f v = max # naming v to not collide w/ blue s = if max != 0 then 255.0 * delta / max else 0.0 end h = if s != 0 then if r == max then 0.0 + (g - b) / delta elsif g == max then 2.0 + (b - r) / delta elsif b == max then 4.0 + (r - g) / delta end else -1.0 end h *= 60 h += 360.0 if h < 0 s *= 100.0 / 255 v *= 100.0 / 255 return [h, s, v] end seen = {} good = {} DATA.readlines.map { |l| l.split }.each do |rgb, name| next if name =~ /\d$/ next if seen[rgb] h, s, v = rgb2hsb(*rgb.scan(/../).map { |s| s.to_i(16) }) next unless s > 66.67 unless v == 0 # skip weak colors, black is not weak next if (50..80).include? h # skip yellowish colors, weak on white seen[rgb] = true good[name] = [h, s, v] end names = good.sort_by { |n, (h, s, v)| [v, h] }.map { |n, _| n } # 6 colors on the color hexagon, but we took out yellow. Spread out # all colors as evenly as possible. We can't transpose non-rectangular # matricies, so we have to fill it in with nils and remove them later. bucket_size, leftover = names.size.divmod 5 groups = names.enum_slice(bucket_size).to_a filler = [nil] * (bucket_size - leftover) # must be rectangular to transpose groups.last.push(*filler) groups = groups.transpose.flatten.compact p groups.size puts "%w[#{groups.join(" ")}]" __END__ # ...huge color list excluded... |