1 """Generate RGB colours suitable for distinguishing categorical data.
2
3 This module provides a class that implements a spiral 'path' through HSV
4 colour space, permitting the selection of a number of points along that path,
5 and returning the output in RGB colour space, suitable for use with ReportLab
6 and other graphics packages.
7
8 This approach to colour choice was inspired by Bang Wong's Points of View
9 article: Color Coding, in Nature Methods _7_ 573 (doi:10.1038/nmeth0810-573).
10
11 The module also provides helper functions that return a list for colours, or
12 a dictionary of colours (if passed an iterable containing the names of
13 categories to be coloured).
14 """
15
16
17 import colorsys
18 from math import log, exp, floor, pi
19 import random
20
21
23 """Implement a spiral path through HSV colour space.
24
25 This class provides functions for sampling points along a logarithmic
26 spiral path through HSV colour space.
27
28 The spiral is described by r = a * exp(b * t) where r is the distance
29 from the axis of the HSV cylinder to the current point in the spiral,
30 and t is the angle through which the spiral has turned to reach the
31 current point. a and b are (positive, real) parameters that control the
32 shape of the spiral.
33
34 a: the starting direction of the spiral
35 b: the number of revolutions about the axis made by the spiral
36
37 We permit the spiral to move along the cylinder ('in V-space') between
38 v_init and v_final, to give a gradation in V (essentially, brightness),
39 along the path, where v_init, v_final are in [0,1].
40
41 A brightness 'jitter' may also be provided as an absolute value in
42 V-space, to aid in distinguishing consecutive colour points on the
43 path.
44 """
45 - def __init__(self, a=1, b=0.33, v_init=0.85, v_final=0.5,
46 jitter=0.05):
47 """Initialise a logarithmic spiral path through HSV colour space
48
49 Arguments:
50
51 o a - Parameter a for the spiral, controls the initial spiral
52 direction. a > 0
53
54 o b - parameter b for the spiral, controls the rate at which the
55 spiral revolves around the axis. b > 0
56
57 o v_init - initial value of V (brightness) for the spiral.
58 v_init in [0,1]
59
60 o v_final - final value of V (brightness) for the spiral
61 v_final in [0,1]
62
63 o jitter - the degree of V (brightness) jitter to add to each
64 selected colour. The amount of jitter will be selected
65 from a uniform random distribution [-jitter, jitter],
66 and V will be maintained in [0,1].
67 """
68
69 self.a = a
70 self.b = b
71 self.v_init = v_init
72 self.v_final = v_final
73 self.jitter = jitter
74
76 """Generate k different RBG colours evenly-space on the spiral.
77
78 A generator returning the RGB colour space values for k
79 evenly-spaced points along the defined spiral in HSV space.
80
81 Arguments:
82
83 o k - the number of points to return
84
85 o offset - how far along the spiral path to start.
86 """
87
88 assert offset > 0 and offset < 1, "offset must be in (0,1)"
89 v_rate = (self._v_final - self._v_init) / float(k)
90
91
92 for n in range(1, k+1):
93
94
95 t = (1./self._b) * (log(n + (k * offset)) -
96 log((1 + offset) * k * self._a))
97
98
99 h = t
100 while h < 0:
101 h += 2 * pi
102 h = (h - (floor(h/(2 * pi)) * pi))
103
104 h = h / (2 * pi)
105
106 r = self._a * exp(self._b * t)
107
108
109
110 if self._jitter:
111 jitter = random.random() * 2 * self._jitter - self._jitter
112 else:
113 jitter = 0
114 v = self._v_init + (n * v_rate + jitter)
115
116
117 yield colorsys.hsv_to_rgb(h, r, max(0, min(v, 1)))
118
121
124
127
130
133
136
139
142
145
148
149 a = property(_get_a, _set_a,
150 doc="Parameter controlling initial spiral direction (a > 0)")
151 b = property(_get_b, _set_b,
152 doc="Parameter controlling rate spiral revolves around axis (b > 0)")
153 v_init = property(_get_v_init, _set_v_init,
154 doc="Initial value of V (brightness) for the spiral (range 0 to 1)")
155 v_final = property(_get_v_final, _set_v_final,
156 doc="Final value of V (brightness) for the spiral (range 0 to 1)")
157 jitter = property(_get_jitter, _set_jitter,
158 doc="Degree of V (brightness) jitter to add to each color (range 0 to 1)")
159
160
161
162
164 """Returns k colours selected by the ColorSpiral object, as a generator.
165
166 Arguments:
167
168 o k - the number of colours to return
169
170 o **kwargs - pass-through arguments to the ColorSpiral object
171 """
172 cs = ColorSpiral(**kwargs)
173 return cs.get_colors(k)
174
175
177 """Returns a dictionary of colours using the provided values as keys.
178
179 Returns a dictionary, keyed by the members of iterable l, with a
180 colour assigned to each member.
181
182 Arguments:
183
184 o l - an iterable representing classes to be coloured
185
186 o **kwargs - pass-through arguments to the ColorSpiral object
187 """
188 cs = ColorSpiral(**kwargs)
189 colors = cs.get_colors(len(l))
190 dict = {}
191 for item in l:
192 dict[item] = colors.next()
193 return dict
194