2006-11-26

Color Scheme Generator for Charts

Sometimes one needs to show many data series on one chart. In such cases, usually distinction between those data series is achieved by using different colors for each data set. Most charting libraries leave colors selection to developer. This can be problem when there is need to choose more than six colors which can be easily distinguished by most people. Below you can find Python program which can be helpful.

Program generates palette of colors where each color can be easily distinguished from other colors in its neighborhood. Difference between colors is computed using W3C's formula for color contrast. Minimum contrast (brightness and color), palette size, neighborhood diameter and maximum number of iterations can be easily set in first few lines of program code.

New colors are created using pseudo-random generators and tested for minimum contrast. If contrast test fails, the color is dropped and new one is created. Besides of minimum required contrast, the size of neighborhood has great impact on probability that test will fail. Too big neighborhood diameter could make it impossible to create complete neighborhood and lock the program. To prevent this, there is limit for number of iterations.
#!/usr/bin/python

from random import randint

class Color:
   __c = [0, 0, 0]
   __cB = None
   def __init__(self, r=-1, g=-1, b=-1):
       if r == -1:
           r = randint(1,255)
       if g == -1:
           g = randint(1,255)
       if b == -1:
           b = randint(1,255)
       self.__c = [r, g, b]
   def __getitem__(self, key):
       return self.__c[key]
   def __setitem__(self, key, value):
       self.__c[key] = value
       self.__cB = None
   def getBritness(self):
       if self.__cB:
           return self.__cB
       else:
           self.__cB = ((self.__c[0] * 299) +
                       (self.__c[1] * 587) +
                       (self.__c[2] * 114)) / 1000
           return self.__cB
   def __sub__(self, other):
       return ((max(self.__c[0], other[0]) - min(self.__c[0], other[0])) +
               (max(self[1], other[1]) - min(self[1], other[1])) +
               (max(self[2], other[2]) - min(self[2], other[2])))
   def __str__(self):
       return "%02x%02x%02x" % (self.__c[0], self.__c[1], self.__c[2])

colors = []
num = 25
comparedNum = 5

c = Color()
colors.append(c)
stopper = 100000

minBrit = 35
minColDiff = 100

print "searching...",
for n in range(num)[1:]:
   while stopper:
       newC = Color()
       passed = 0
       for back in range(comparedNum):
           testedNum = n-(back+1)
           if testedNum < 0:
               passed += 1
               continue
           c = colors[testedNum]
           bDiff = abs(c.getBritness() - newC.getBritness())
           cDiff = c - newC
           if  cDiff >= minColDiff and  bDiff > minBrit:
               passed += 1
           else:
               break
       if passed == comparedNum:
           colors.append(newC)
           print "found:", newC,
           break

       stopper -= 1

if not stopper > 0:
   print "searching failed"
else:
   print "finished"


html = """
< head>

< /head>
< body>
""" % (800/n)

python = "colors = ("

for c in colors:
   html += '
\n' % c python += '"%s", ' % c python = python[:-2] + ")" html += "< /body>" out = file("colors.html", "w") print >>out, html print python out.close()