diff options
Diffstat (limited to 'sugarpycha/color.py')
-rw-r--r-- | sugarpycha/color.py | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/sugarpycha/color.py b/sugarpycha/color.py new file mode 100644 index 0000000..82b436f --- /dev/null +++ b/sugarpycha/color.py @@ -0,0 +1,204 @@ +# Copyright(c) 2007-2010 by Lorenzo Gil Sanchez <lorenzo.gil.sanchez@gmail.com> +# 2009 by Yaco S.L. <lgs@yaco.es> +# +# This file is part of PyCha. +# +# PyCha is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# PyCha is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PyCha. If not, see <http://www.gnu.org/licenses/>. + +import math + +from pycha.utils import clamp + + +DEFAULT_COLOR = '#3c581a' + + +def hex2rgb(hexstring, digits=2): + """Converts a hexstring color to a rgb tuple. + + Example: #ff0000 -> (1.0, 0.0, 0.0) + + digits is an integer number telling how many characters should be + interpreted for each component in the hexstring. + """ + if isinstance(hexstring, (tuple, list)): + return hexstring + + top = float(int(digits * 'f', 16)) + r = int(hexstring[1:digits + 1], 16) + g = int(hexstring[digits + 1:digits * 2 + 1], 16) + b = int(hexstring[digits * 2 + 1:digits * 3 + 1], 16) + return r / top, g / top, b / top + + +def rgb2hsv(r, g, b): + """Converts a RGB color into a HSV one + + See http://en.wikipedia.org/wiki/HSV_color_space + """ + maximum = max(r, g, b) + minimum = min(r, g, b) + if maximum == minimum: + h = 0.0 + elif maximum == r: + h = 60.0 * ((g - b) / (maximum - minimum)) + 360.0 + if h >= 360.0: + h -= 360.0 + elif maximum == g: + h = 60.0 * ((b - r) / (maximum - minimum)) + 120.0 + elif maximum == b: + h = 60.0 * ((r - g) / (maximum - minimum)) + 240.0 + + if maximum == 0.0: + s = 0.0 + else: + s = 1.0 - (minimum / maximum) + + v = maximum + + return h, s, v + + +def hsv2rgb(h, s, v): + """Converts a HSV color into a RGB one + + See http://en.wikipedia.org/wiki/HSV_color_space + """ + hi = int(math.floor(h / 60.0)) % 6 + f = (h / 60.0) - hi + p = v * (1 - s) + q = v * (1 - f * s) + t = v * (1 - (1 - f) * s) + + if hi == 0: + r, g, b = v, t, p + elif hi == 1: + r, g, b = q, v, p + elif hi == 2: + r, g, b = p, v, t + elif hi == 3: + r, g, b = p, q, v + elif hi == 4: + r, g, b = t, p, v + elif hi == 5: + r, g, b = v, p, q + + return r, g, b + + +def lighten(r, g, b, amount): + """Return a lighter version of the color (r, g, b)""" + return (clamp(0.0, 1.0, r + amount), + clamp(0.0, 1.0, g + amount), + clamp(0.0, 1.0, b + amount)) + + +basicColors = dict( + red='#6d1d1d', + green=DEFAULT_COLOR, + blue='#224565', + grey='#444444', + black='#000000', + darkcyan='#305755', + ) + + +class ColorSchemeMetaclass(type): + """This metaclass is used to autoregister all ColorScheme classes""" + + def __new__(mcs, name, bases, dict): + klass = type.__new__(mcs, name, bases, dict) + klass.registerColorScheme() + return klass + + +class ColorScheme(dict): + """A color scheme is a dictionary where the keys match the keys + constructor argument and the values are colors""" + + __metaclass__ = ColorSchemeMetaclass + __registry__ = {} + + def __init__(self, keys): + super(ColorScheme, self).__init__() + + @classmethod + def registerColorScheme(cls): + key = cls.__name__.replace('ColorScheme', '').lower() + if key: + cls.__registry__[key] = cls + + @classmethod + def getColorScheme(cls, name, default=None): + return cls.__registry__.get(name, default) + + +class GradientColorScheme(ColorScheme): + """In this color scheme each color is a lighter version of initialColor. + + This difference is computed based on the number of keys. + + The initialColor is given in a hex string format. + """ + + def __init__(self, keys, initialColor=DEFAULT_COLOR): + super(GradientColorScheme, self).__init__(keys) + if initialColor in basicColors: + initialColor = basicColors[initialColor] + + r, g, b = hex2rgb(initialColor) + light = 1.0 / (len(keys) * 2) + + for i, key in enumerate(keys): + self[key] = lighten(r, g, b, light * i) + + +class FixedColorScheme(ColorScheme): + """In this color scheme fixed colors are used. + + These colors are provided as a list argument in the constructor. + """ + + def __init__(self, keys, colors=[]): + super(FixedColorScheme, self).__init__(keys) + + if len(keys) != len(colors): + raise ValueError("You must provide as many colors as datasets " + "for the fixed color scheme") + + for i, key in enumerate(keys): + self[key] = hex2rgb(colors[i]) + + +class RainbowColorScheme(ColorScheme): + """In this color scheme the rainbow is divided in N pieces + where N is the number of datasets. + + So each dataset gets a color of the rainbow. + """ + + def __init__(self, keys, initialColor=DEFAULT_COLOR): + super(RainbowColorScheme, self).__init__(keys) + if initialColor in basicColors: + initialColor = basicColors[initialColor] + + r, g, b = hex2rgb(initialColor) + h, s, v = rgb2hsv(r, g, b) + + angleDelta = 360.0 / (len(keys) + 1) + for key in keys: + self[key] = hsv2rgb(h, s, v) + h += angleDelta + if h >= 360.0: + h -= 360.0 |