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