root/trunk/pycha/color.py

Revision 171, 5.7 kB (checked in by lgs, 18 months ago)

Add a docstring for the rainbow color scheme

Line 
1# Copyright(c) 2007-2009 by Lorenzo Gil Sanchez <lorenzo.gil.sanchez@gmail.com>
2#              2009 by Yaco S.L. <lgs@yaco.es>
3#
4# This file is part of PyCha.
5#
6# PyCha is free software: you can redistribute it and/or modify
7# it under the terms of the GNU Lesser General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# PyCha is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU Lesser General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public License
17# along with PyCha.  If not, see <http://www.gnu.org/licenses/>.
18
19import math
20
21DEFAULT_COLOR = '#3c581a'
22
23
24def clamp(minValue, maxValue, value):
25    """Make sure value is between minValue and maxValue"""
26    if value < minValue:
27        return minValue
28    if value > maxValue:
29        return maxValue
30    return value
31
32
33def hex2rgb(hexstring, digits=2):
34    """Converts a hexstring color to a rgb tuple.
35
36    Example: #ff0000 -> (1.0, 0.0, 0.0)
37
38    digits is an integer number telling how many characters should be
39    interpreted for each component in the hexstring.
40    """
41    if isinstance(hexstring, (tuple, list)):
42        return hexstring
43
44    top = float(int(digits * 'f', 16))
45    r = int(hexstring[1:digits+1], 16)
46    g = int(hexstring[digits+1:digits*2+1], 16)
47    b = int(hexstring[digits*2+1:digits*3+1], 16)
48    return r / top, g / top, b / top
49
50
51def rgb2hsv(r, g, b):
52    """Converts a RGB color into a HSV one
53
54    See http://en.wikipedia.org/wiki/HSV_color_space
55    """
56    maximum = max(r, g, b)
57    minimum = min(r, g, b)
58    if maximum == minimum:
59        h = 0.0
60    elif maximum == r:
61        h = 60.0 * ((g - b) / (maximum - minimum)) + 360.0
62        if h >= 360.0:
63            h -= 360.0
64    elif maximum == g:
65        h = 60.0 * ((b - r) / (maximum - minimum)) + 120.0
66    elif maximum == b:
67        h = 60.0 * ((r - g) / (maximum - minimum)) + 240.0
68
69    if maximum == 0.0:
70        s = 0.0
71    else:
72        s = 1.0 - (minimum / maximum)
73
74    v = maximum
75
76    return h, s, v
77
78
79def hsv2rgb(h, s, v):
80    """Converts a HSV color into a RGB one
81
82    See http://en.wikipedia.org/wiki/HSV_color_space
83    """
84    hi = int(math.floor(h / 60.0)) % 6
85    f = (h / 60.0) - hi
86    p = v * (1 - s)
87    q = v * (1 - f * s)
88    t = v * (1 - (1 - f) * s)
89
90    if hi == 0:
91        r, g, b = v, t, p
92    elif hi == 1:
93        r, g, b = q, v, p
94    elif hi == 2:
95        r, g, b = p, v, t
96    elif hi == 3:
97        r, g, b = p, q, v
98    elif hi == 4:
99        r, g, b = t, p, v
100    elif hi == 5:
101        r, g, b = v, p, q
102
103    return r, g, b
104
105
106def lighten(r, g, b, amount):
107    """Return a lighter version of the color (r, g, b)"""
108    return (clamp(0.0, 1.0, r + amount),
109            clamp(0.0, 1.0, g + amount),
110            clamp(0.0, 1.0, b + amount))
111
112
113basicColors = dict(
114    red='#6d1d1d',
115    green=DEFAULT_COLOR,
116    blue='#224565',
117    grey='#444444',
118    black='#000000',
119    darkcyan='#305755',
120    )
121
122
123class ColorSchemeMetaclass(type):
124    """This metaclass is used to autoregister all ColorScheme classes"""
125
126    def __new__(mcs, name, bases, dict):
127        klass = type.__new__(mcs, name, bases, dict)
128        klass.registerColorScheme()
129        return klass
130
131
132class ColorScheme(dict):
133    """A color scheme is a dictionary where the keys match the keys
134    constructor argument and the values are colors"""
135
136    __metaclass__ = ColorSchemeMetaclass
137    __registry__ = {}
138
139    def __init__(self, keys):
140        super(ColorScheme, self).__init__()
141
142    @classmethod
143    def registerColorScheme(cls):
144        key = cls.__name__.replace('ColorScheme', '').lower()
145        if key:
146            cls.__registry__[key] = cls
147
148    @classmethod
149    def getColorScheme(cls, name, default=None):
150        return cls.__registry__.get(name, default)
151
152
153class GradientColorScheme(ColorScheme):
154    """In this color scheme each color is a lighter version of initialColor.
155
156    This difference is computed based on the number of keys.
157
158    The initialColor is given in a hex string format.
159    """
160
161    def __init__(self, keys, initialColor=DEFAULT_COLOR):
162        super(GradientColorScheme, self).__init__(keys)
163        if initialColor in basicColors:
164            initialColor = basicColors[initialColor]
165
166        r, g, b = hex2rgb(initialColor)
167        light = 1.0 / (len(keys) * 2)
168
169        for i, key in enumerate(keys):
170            self[key] = lighten(r, g, b, light * i)
171
172
173class FixedColorScheme(ColorScheme):
174    """In this color scheme fixed colors are used.
175
176    These colors are provided as a list argument in the constructor.
177    """
178
179    def __init__(self, keys, colors=[]):
180        super(FixedColorScheme, self).__init__(keys)
181
182        if len(keys) != len(colors):
183            raise ValueError("You must provide as many colors as datasets "
184                             "for the fixed color scheme")
185
186        for i, key in enumerate(keys):
187            self[key] = hex2rgb(colors[i])
188
189
190class RainbowColorScheme(ColorScheme):
191    """In this color scheme the rainbow is divided in N pieces
192    where N is the number of datasets.
193
194    So each dataset gets a color of the rainbow.
195    """
196
197    def __init__(self, keys, initialColor=DEFAULT_COLOR):
198        super(RainbowColorScheme, self).__init__(keys)
199        if initialColor in basicColors:
200            initialColor = basicColors[initialColor]
201
202        r, g, b = hex2rgb(initialColor)
203        h, s, v = rgb2hsv(r, g, b)
204
205        angleDelta = 360.0 / (len(keys) + 1)
206        for key in keys:
207            self[key] = hsv2rgb(h, s, v)
208            h += angleDelta
209            if h >= 360.0:
210                h -= 360.0
Note: See TracBrowser for help on using the browser.