"""
Colors
This module defines a :class:`Color` object and helper functions (see,
e.g., :func:`hue`, :func:`rainbow`), as well as a set of
:data:`colors` and :data:`colormaps` to use with
:class:`Graphics` objects in Sage.
For a list of pre-defined colors in Sage, evaluate::
sage: sorted(colors)
['aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'automatic', ...]
Apart from 'automatic' which just an alias for 'lightblue', this list
comprises the "official" W3C CSS3_ / SVG_ colors.
.. _CSS3: http://www.w3.org/TR/css3-color/#svg-color
.. _SVG: http://www.w3.org/TR/SVG/types.html#ColorKeywords
For a list of color maps in Sage, evaluate::
sage: sorted(colormaps)
['Accent', 'Accent_r', 'Blues', 'Blues_r', 'BrBG', 'BrBG_r', ...]
These are imported from matplotlib's cm_ module.
.. _cm: http://matplotlib.sourceforge.net/api/cm_api.html
"""
import math
import collections
from colorsys import hsv_to_rgb, hls_to_rgb, rgb_to_hsv, rgb_to_hls
cm = None
colors_dict = {
'automatic' : '#add8e6',
'aliceblue' : '#f0f8ff',
'antiquewhite' : '#faebd7',
'aqua' : '#00ffff',
'aquamarine' : '#7fffd4',
'azure' : '#f0ffff',
'beige' : '#f5f5dc',
'bisque' : '#ffe4c4',
'black' : '#000000',
'blanchedalmond' : '#ffebcd',
'blue' : '#0000ff',
'blueviolet' : '#8a2be2',
'brown' : '#a52a2a',
'burlywood' : '#deb887',
'cadetblue' : '#5f9ea0',
'chartreuse' : '#7fff00',
'chocolate' : '#d2691e',
'coral' : '#ff7f50',
'cornflowerblue' : '#6495ed',
'cornsilk' : '#fff8dc',
'crimson' : '#dc143c',
'cyan' : '#00ffff',
'darkblue' : '#00008b',
'darkcyan' : '#008b8b',
'darkgoldenrod' : '#b8860b',
'darkgray' : '#a9a9a9',
'darkgreen' : '#006400',
'darkgrey' : '#a9a9a9',
'darkkhaki' : '#bdb76b',
'darkmagenta' : '#8b008b',
'darkolivegreen' : '#556b2f',
'darkorange' : '#ff8c00',
'darkorchid' : '#9932cc',
'darkred' : '#8b0000',
'darksalmon' : '#e9967a',
'darkseagreen' : '#8fbc8f',
'darkslateblue' : '#483d8b',
'darkslategray' : '#2f4f4f',
'darkslategrey' : '#2f4f4f',
'darkturquoise' : '#00ced1',
'darkviolet' : '#9400d3',
'deeppink' : '#ff1493',
'deepskyblue' : '#00bfff',
'dimgray' : '#696969',
'dimgrey' : '#696969',
'dodgerblue' : '#1e90ff',
'firebrick' : '#b22222',
'floralwhite' : '#fffaf0',
'forestgreen' : '#228b22',
'fuchsia' : '#ff00ff',
'gainsboro' : '#dcdcdc',
'ghostwhite' : '#f8f8ff',
'gold' : '#ffd700',
'goldenrod' : '#daa520',
'gray' : '#808080',
'green' : '#008000',
'greenyellow' : '#adff2f',
'grey' : '#808080',
'honeydew' : '#f0fff0',
'hotpink' : '#ff69b4',
'indianred' : '#cd5c5c',
'indigo' : '#4b0082',
'ivory' : '#fffff0',
'khaki' : '#f0e68c',
'lavender' : '#e6e6fa',
'lavenderblush' : '#fff0f5',
'lawngreen' : '#7cfc00',
'lemonchiffon' : '#fffacd',
'lightblue' : '#add8e6',
'lightcoral' : '#f08080',
'lightcyan' : '#e0ffff',
'lightgoldenrodyellow' : '#fafad2',
'lightgray' : '#d3d3d3',
'lightgreen' : '#90ee90',
'lightgrey' : '#d3d3d3',
'lightpink' : '#ffb6c1',
'lightsalmon' : '#ffa07a',
'lightseagreen' : '#20b2aa',
'lightskyblue' : '#87cefa',
'lightslategray' : '#778899',
'lightslategrey' : '#778899',
'lightsteelblue' : '#b0c4de',
'lightyellow' : '#ffffe0',
'lime' : '#00ff00',
'limegreen' : '#32cd32',
'linen' : '#faf0e6',
'magenta' : '#ff00ff',
'maroon' : '#800000',
'mediumaquamarine' : '#66cdaa',
'mediumblue' : '#0000cd',
'mediumorchid' : '#ba55d3',
'mediumpurple' : '#9370db',
'mediumseagreen' : '#3cb371',
'mediumslateblue' : '#7b68ee',
'mediumspringgreen' : '#00fa9a',
'mediumturquoise' : '#48d1cc',
'mediumvioletred' : '#c71585',
'midnightblue' : '#191970',
'mintcream' : '#f5fffa',
'mistyrose' : '#ffe4e1',
'moccasin' : '#ffe4b5',
'navajowhite' : '#ffdead',
'navy' : '#000080',
'oldlace' : '#fdf5e6',
'olive' : '#808000',
'olivedrab' : '#6b8e23',
'orange' : '#ffa500',
'orangered' : '#ff4500',
'orchid' : '#da70d6',
'palegoldenrod' : '#eee8aa',
'palegreen' : '#98fb98',
'paleturquoise' : '#afeeee',
'palevioletred' : '#db7093',
'papayawhip' : '#ffefd5',
'peachpuff' : '#ffdab9',
'peru' : '#cd853f',
'pink' : '#ffc0cb',
'plum' : '#dda0dd',
'powderblue' : '#b0e0e6',
'purple' : '#800080',
'red' : '#ff0000',
'rosybrown' : '#bc8f8f',
'royalblue' : '#4169e1',
'saddlebrown' : '#8b4513',
'salmon' : '#fa8072',
'sandybrown' : '#f4a460',
'seagreen' : '#2e8b57',
'seashell' : '#fff5ee',
'sienna' : '#a0522d',
'silver' : '#c0c0c0',
'skyblue' : '#87ceeb',
'slateblue' : '#6a5acd',
'slategray' : '#708090',
'slategrey' : '#708090',
'snow' : '#fffafa',
'springgreen' : '#00ff7f',
'steelblue' : '#4682b4',
'tan' : '#d2b48c',
'teal' : '#008080',
'thistle' : '#d8bfd8',
'tomato' : '#ff6347',
'turquoise' : '#40e0d0',
'violet' : '#ee82ee',
'wheat' : '#f5deb3',
'white' : '#ffffff',
'whitesmoke' : '#f5f5f5',
'yellow' : '#ffff00',
'yellowgreen' : '#9acd32'
}
def mod_one(x):
"""
Reduce a number modulo 1.
INPUT:
- ``x`` - an instance of Integer, int, RealNumber, etc.; the
number to reduce
OUTPUT:
- a float
EXAMPLES::
sage: from sage.plot.colors import mod_one
sage: mod_one(1)
1.0
sage: mod_one(7.0)
0.0
sage: mod_one(-11/7)
0.4285714285714286
sage: mod_one(pi) + mod_one(-pi)
1.0
"""
x = float(x)
if x != 1:
x = math.modf(x)[0]
if x < 0:
x += 1
return x
def html_to_float(c):
"""
Convert a HTML hex color to a Red-Green-Blue (RGB) tuple.
INPUT:
- ``c`` - a string; a valid HTML hex color
OUTPUT:
- a RGB 3-tuple of floats in the interval [0.0, 1.0]
EXAMPLES::
sage: from sage.plot.colors import html_to_float
sage: html_to_float('#fff')
(1.0, 1.0, 1.0)
sage: html_to_float('#abcdef')
(0.6705882352941176, 0.803921568627451, 0.9372549019607843)
sage: html_to_float('#123xyz')
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 16: '3x'
"""
if not len(c) or c[0] != '#':
raise ValueError("'%s' must be a valid HTML hex color (e.g., '#f07' or '#d6e7da')" % c)
h = c[1:]
if len(h) == 3:
h = '%s%s%s%s%s%s' % (h[0], h[0], h[1], h[1], h[2], h[2])
elif len(h) != 6:
raise ValueError("color hex string (= '%s') must have length 3 or 6" % h)
return tuple([int(h[i:i + 2], base=16) / float(255) for i in [0, 2, 4]])
def rgbcolor(c, space='rgb'):
"""
Convert a color (string, tuple, list, or :class:`Color`) to a
mod-one reduced (see :func:`mod_one`) valid Red-Green-Blue (RGB)
tuple. The returned tuple is also a valid matplotlib RGB color.
INPUT:
- ``c`` - a :class:`Color` instance, string (name or HTML hex),
3-tuple, or 3-list; the color to convert
- ``space`` - a string (default: 'rgb'); the color space
coordinate system (other choices are 'hsl', 'hls', and 'hsv') in
which to interpret a 3-tuple or 3-list
OUTPUT:
- a RGB 3-tuple of floats in the interval [0.0, 1.0]
EXAMPLES::
sage: from sage.plot.colors import rgbcolor
sage: rgbcolor(Color(0.25, 0.4, 0.9))
(0.25, 0.4, 0.9)
sage: rgbcolor('purple')
(0.5019607843137255, 0.0, 0.5019607843137255)
sage: rgbcolor('#fa0')
(1.0, 0.6666666666666666, 0.0)
sage: rgbcolor('#ffffff')
(1.0, 1.0, 1.0)
sage: rgbcolor((1,1/2,1/3))
(1.0, 0.5, 0.3333333333333333)
sage: rgbcolor([1,1/2,1/3])
(1.0, 0.5, 0.3333333333333333)
sage: rgbcolor((1,1,1), space='hsv')
(1.0, 0.0, 0.0)
sage: rgbcolor((0.5,0.75,1), space='hls')
(0.5, 0.9999999999999999, 1.0)
sage: rgbcolor((0.5,1.0,0.75), space='hsl')
(0.5, 0.9999999999999999, 1.0)
sage: rgbcolor([1,2,255]) # WARNING -- numbers are reduced mod 1!!
(1.0, 0.0, 0.0)
sage: rgbcolor('#abcd')
Traceback (most recent call last):
...
ValueError: color hex string (= 'abcd') must have length 3 or 6
sage: rgbcolor('fff')
Traceback (most recent call last):
...
ValueError: unknown color 'fff'
sage: rgbcolor(1)
Traceback (most recent call last):
...
TypeError: '1' must be a Color, list, tuple, or string
sage: rgbcolor((0.2,0.8,1), space='grassmann')
Traceback (most recent call last):
...
ValueError: space must be one of 'rgb', 'hsv', 'hsl', 'hls'
sage: rgbcolor([0.4, 0.1])
Traceback (most recent call last):
...
ValueError: color list or tuple '[0.400000000000000, 0.100000000000000]' must have 3 entries, one for each RGB, HSV, HLS, or HSL channel
"""
if isinstance(c, Color):
return c.rgb()
if isinstance(c, str):
if len(c) > 0 and c[0] == '#':
return html_to_float(c)
else:
try:
return colors[c].rgb()
except KeyError:
raise ValueError("unknown color '%s'" % c)
elif isinstance(c, (list, tuple)):
if len(c) != 3:
raise ValueError("color list or tuple '%s' must have 3 entries, one for each RGB, HSV, HLS, or HSL channel" % (c, ))
c = map(mod_one, list(c))
if space == 'rgb':
return tuple(c)
elif space == 'hsv':
return tuple(map(float, hsv_to_rgb(*c)))
elif space == 'hls':
return tuple(map(float, hls_to_rgb(*c)))
elif space == 'hsl':
return tuple(map(float, hls_to_rgb(c[0], c[2], c[1])))
else:
raise ValueError("space must be one of 'rgb', 'hsv', 'hsl', 'hls'")
raise TypeError("'%s' must be a Color, list, tuple, or string" % c)
to_mpl_color = rgbcolor
class Color(object):
def __init__(self, r='#0000ff', g=None, b=None, space='rgb'):
"""
An Red-Green-Blue (RGB) color model color object. For most
consumer-grade devices (e.g., CRTs, LCDs, and printers), as
well as internet applications, this is a point in the sRGB
absolute color space. The Hue-Saturation-Lightness (HSL),
Hue-Lightness-Saturation (HLS), and Hue-Saturation-Value (HSV)
spaces are useful alternate representations, or coordinate
transformations, of this space. Coordinates in all of these
spaces are floating point values in the interval [0.0, 1.0].
.. note:: All instantiations of :class:`Color` are converted
to an internal RGB floating point 3-tuple. This is
likely to degrade precision.
INPUT:
- ``r,g,b`` - either a triple of floats between 0 and 1,
OR ``r`` - a color name string or HTML color hex string
- ``space`` - a string (default: 'rgb'); the coordinate system
(other choices are 'hsl', 'hls', and 'hsv') in which to
interpret a triple of floats
EXAMPLES::
sage: Color('purple')
RGB color (0.5019607843137255, 0.0, 0.5019607843137255)
sage: Color('#8000ff')
RGB color (0.5019607843137255, 0.0, 1.0)
sage: Color(0.5,0,1)
RGB color (0.5, 0.0, 1.0)
sage: Color(0.5, 1.0, 1, space='hsv')
RGB color (0.0, 1.0, 1.0)
sage: Color(0.25, 0.5, 0.5, space='hls')
RGB color (0.5000000000000001, 0.75, 0.25)
sage: Color(1, 0, 1/3, space='hsl')
RGB color (0.3333333333333333, 0.3333333333333333, 0.3333333333333333)
sage: from sage.plot.colors import chocolate
sage: Color(chocolate)
RGB color (0.8235294117647058, 0.4117647058823529, 0.11764705882352941)
"""
if g is None and b is None:
self.__rgb = rgbcolor(r)
else:
self.__rgb = rgbcolor((r, g, b), space=space)
def __repr__(self):
"""
Return a string representation of this color.
OUTPUT:
- a string
EXAMPLES::
sage: Color('#8000ff').__repr__()
'RGB color (0.5019607843137255, 0.0, 1.0)'
sage: Color(1, 0.5, 1/16, space='hsl').__repr__()
'RGB color (0.09375, 0.03125, 0.03125)'
"""
return "RGB color %s" % (self.__rgb, )
def __lt__(self, right):
"""
Check whether a :class:`Color` object is less than some other
object. This doesn't make sense, and so we conclude that it is
not less than the other object.
INPUT:
- ``right`` - an object
OUTPUT:
- boolean - False
EXAMPLES::
sage: Color('red') < Color('red')
False
sage: Color('blue') < Color('red')
False
sage: Color('red') < "xyzzy"
False
"""
return False
def __le__(self, right):
"""
Check whether a :class:`Color` object is less than or equal to
some other object. It wouldn't make sense for it to be less than
the other object, so we treat this the same as an equality
check.
INPUT:
- ``right`` - an object
OUTPUT:
- boolean - False
EXAMPLES::
sage: Color('red') <= Color('red')
True
sage: Color('blue') <= Color('red')
False
sage: Color('red') <= "xyzzy"
False
"""
return self == right
def __eq__(self, right):
"""
Compare two :class:`Color` objects to determine whether
they refer to the same color.
INPUT:
- ``right`` - a :class:`Color` instance
OUTPUT:
- boolean - True if the two colors are the same, False if different
EXAMPLES::
sage: Color('red') == Color((1,0,0))
True
sage: Color('blue') == Color((0,1,0))
False
sage: Color('blue') + Color((0,1,0)) == Color((0,0.5,0.5))
True
sage: Color(0.2,0.3,0.2) == False
False
"""
if isinstance(right, Color):
return self.__rgb == right.__rgb
else:
return False
def __ne__(self, right):
"""
Compare two :class:`Color` objects to determine whether
they refer to different colors.
INPUT:
- ``right`` - a :class:`Color` instance
OUTPUT:
- boolean - True if the two colors are different,
False if they're the same
EXAMPLES::
sage: Color('green') != Color('yellow')
True
sage: Color('red') != Color(1,0,0)
False
sage: Color('yellow') != Color(1,1,0)
False
sage: Color('blue') != 23
True
"""
return not (self == right)
def __gt__(self, right):
"""
Check whether a :class:`Color` object is greater than some other
object. This doesn't make sense, and so we conclude that it is
not greater than the other object.
INPUT:
- ``right`` - an object
OUTPUT:
- boolean - False
EXAMPLES::
sage: Color('red') > Color('red')
False
sage: Color('blue') > Color('red')
False
sage: Color('red') > "xyzzy"
False
"""
return False
def __ge__(self, right):
"""
Check whether a :class:`Color` object is greater than or equal
to some other object. It wouldn't make sense for it to be
greater than the other object, so we treat this the same as an
equality check.
INPUT:
- ``right`` - an object
OUTPUT:
- boolean - False
EXAMPLES::
sage: Color('red') >= Color('red')
True
sage: Color('blue') >= Color('red')
False
sage: Color('red') >= "xyzzy"
False
"""
return self == right
def __hash__(self):
"""
Return the hash value of a color.
Equal colors return equal hash values.
OUTPUT:
- a hash value
EXAMPLES::
sage: hash(Color('red')) # random
873150856
sage: hash(Color('red')) == hash(Color((1,0,0)))
True
"""
return hash(self.__rgb)
def blend(self, color, fraction=0.5):
"""
Return a color blended with the given ``color`` by a given
``fraction``. The algorithm interpolates linearly between the
colors' corresponding R, G, and B coordinates.
INPUT:
- ``color`` - a :class:`Color` instance or float-convertible
3-tuple/list; the color with which to blend this color
- ``fraction`` - a float-convertible number; the fraction of
``color`` to blend with this color
OUTPUT:
- a **new** :class:`Color` instance
EXAMPLES::
sage: from sage.plot.colors import red, blue, lime
sage: red.blend(blue)
RGB color (0.5, 0.0, 0.5)
sage: red.blend(blue, fraction=0.0)
RGB color (1.0, 0.0, 0.0)
sage: red.blend(blue, fraction=1.0)
RGB color (0.0, 0.0, 1.0)
sage: lime.blend((0.3, 0.5, 0.7))
RGB color (0.15, 0.75, 0.35)
sage: blue.blend(blue)
RGB color (0.0, 0.0, 1.0)
sage: red.blend(lime, fraction=0.3)
RGB color (0.7, 0.3, 0.0)
sage: blue.blend((0.0, 0.9, 0.2), fraction=0.2)
RGB color (0.0, 0.18000000000000002, 0.8400000000000001)
sage: red.blend(0.2)
Traceback (most recent call last):
...
TypeError: 0.200000000000000 must be a Color or float-convertible 3-tuple/list
"""
fraction = float(fraction)
if isinstance(color, Color):
color = color.__rgb
if isinstance(color, (list, tuple)) and len(color) == 3:
color = map(float, color)
return Color(rgbcolor([(1 - fraction) * a + fraction * b
for a, b in zip(self.__rgb, color)]))
raise TypeError("%s must be a Color or float-convertible 3-tuple/list" % (color, ))
def __add__(self, right):
"""
Return a color "added" on the right to another color, with
:meth:`blend`.
INPUT:
- ``right`` - a :class:`Color` instance or float-convertible
3-tuple/list
OUTPUT:
- a **new** :class:`Color` instance
EXAMPLES::
sage: from sage.plot.colors import red, blue, lime
sage: red + blue + lime
RGB color (0.25, 0.5, 0.25)
sage: from sage.plot.colors import cyan, magenta, yellow
sage: cyan + magenta + yellow
RGB color (0.75, 0.75, 0.5)
sage: c1 = Color(0.1, 0.5, 0.8); c2 = Color(0.2, 0.4, 0.7, space='hsv')
sage: c1 + 0.1
Traceback (most recent call last):
...
TypeError: 0.100000000000000 must be a Color or float-convertible 3-tuple/list
sage: c2 + [0.5, 0.2, 0.9]
RGB color (0.572, 0.44999999999999996, 0.66)
sage: c1.__add__(red).__add__((0.9, 0.2, 1/3))
RGB color (0.7250000000000001, 0.225, 0.3666666666666667)
sage: c1 + c2
RGB color (0.37199999999999994, 0.6, 0.61)
"""
return self.blend(right)
def __radd__(self, left):
"""
Return a color "added" on the left to another color, with
:meth:`blend`.
INPUT:
- ``left`` - a :class:`Color` instance or float-convertible
3-tuple/list
OUTPUT:
- a **new** :class:`Color` instance
EXAMPLES::
sage: from sage.plot.colors import olive, orchid
sage: olive + orchid
RGB color (0.6784313725490196, 0.47058823529411764, 0.4196078431372549)
sage: d1 = Color(0.1, 0.5, 0.8, space='hls'); d2 = Color(0.2, 0.4, 0.7)
sage: [0.5, 0.2, 0.9] + d2
RGB color (0.35, 0.30000000000000004, 0.8)
sage: 0.1 + d1
Traceback (most recent call last):
...
TypeError: 0.100000000000000 must be a Color or float-convertible 3-tuple/list
sage: d2.__radd__(Color('brown')).__radd__((0.9, 0.2, 1/3))
RGB color (0.661764705882353, 0.2411764705882353, 0.38284313725490193)
"""
return self + left
def __mul__(self, right):
"""
Return a color whose RGB coordinates are this color's
coordinates multiplied on the right by a scalar.
INPUT:
- ``right`` - a float-convertible number
OUTPUT:
- a **new** :class:`Color` instance
EXAMPLES::
sage: Color('yellow') * 0.5
RGB color (0.5, 0.5, 0.0)
sage: Color('yellow') * (9.0 / 8.0) # reduced modulo 1.0
RGB color (0.125, 0.125, 0.0)
sage: from sage.plot.colors import cyan, grey, indianred
sage: cyan * 0.3 + grey * 0.1 + indianred * 0.6
RGB color (0.25372549019607843, 0.1957843137254902, 0.1957843137254902)
sage: indianred.__mul__(42)
RGB color (0.764705882352942, 0.1529411764705877, 0.1529411764705877)
"""
right = float(right)
return Color([x * right for x in self.__rgb])
def __rmul__(self, left):
"""
Return a color whose RGB coordinates are this color's
coordinates multiplied on the left by a scalar.
INPUT:
- ``left`` - a float-convertible number
OUTPUT:
- a **new** :class:`Color` instance
EXAMPLES::
sage: from sage.plot.colors import aqua, cornsilk, tomato
sage: 0.3 * aqua
RGB color (0.0, 0.3, 0.3)
sage: Color('indianred').__rmul__(42)
RGB color (0.764705882352942, 0.1529411764705877, 0.1529411764705877)
"""
return self * left
def __div__(self, right):
"""
Return a color whose RGB coordinates are this color's
coordinates divided by a scalar. This method is called for
"classic division."
INPUT:
- ``right`` - a float-convertible, non-zero number
OUTPUT:
- a **new** instance of :class:`Color`
EXAMPLES::
sage: from sage.plot.colors import papayawhip, yellow
sage: yellow / 4
RGB color (0.25, 0.25, 0.0)
sage: yellow.__div__(4)
RGB color (0.25, 0.25, 0.0)
sage: (papayawhip + Color(0.5, 0.5, 0.1) + yellow) / 3.0
RGB color (0.29166666666666663, 0.286437908496732, 0.07794117647058824)
sage: vector((papayawhip / 2).rgb()) == vector((papayawhip * 0.5).rgb())
True
sage: yellow.__div__(1/4)
RGB color (0.0, 0.0, 0.0)
sage: Color('black') / 0.0
Traceback (most recent call last):
...
ZeroDivisionError: float division by zero
sage: papayawhip / yellow
Traceback (most recent call last):
...
TypeError: float() argument must be a string or a number
"""
return self * (float(1.0) / float(right))
def __truediv__(self, right):
"""
Return a color whose RGB coordinates are this color's
coordinates divided by a scalar. This method is called for
"true division."
INPUT:
- ``right`` - a float-convertible, non-zero number
OUTPUT:
- a **new** instance of :class:`Color`
EXAMPLES::
sage: from __future__ import division
sage: from sage.plot.colors import yellow, gold
sage: yellow / 4
RGB color (0.25, 0.25, 0.0)
sage: yellow.__truediv__(4)
RGB color (0.25, 0.25, 0.0)
sage: gold / pi + yellow * e
RGB color (0.51829585732141..., 0.49333037605210..., 0.0)
"""
return self.__div__(right)
def __hex__(self):
"""
Return a hexadecimal string representation of this color.
This is just the color's HTML hex representation without the
leading '#'.
OUTPUT:
- a string of length 6
EXAMPLES::
sage: from sage.plot.colors import whitesmoke
sage: hex(whitesmoke)
'f5f5f5'
sage: whitesmoke.html_color() == '#' + hex(whitesmoke)
True
sage: hex(Color(0.5, 1.0, 1.0, space='hsv'))
'00ffff'
sage: set([len(hex(Color(t, 1-t, t * t))) for t in srange(0, 1, 0.1)])
set([6])
"""
return self.html_color()[1:]
def __iter__(self):
"""
Return an iterator over the RGB coordinates of this color.
OUTPUT:
- a tupleiterator
EXAMPLES::
sage: from sage.plot.colors import dodgerblue, maroon
sage: r, g, b = dodgerblue
sage: r
0.11764705882352941
sage: g
0.5647058823529412
sage: b
1.0
sage: vector(maroon) == vector(Color(maroon)) == vector(Color('maroon'))
True
"""
return iter(self.__rgb)
def __getitem__(self, i):
"""
Return the Red (0th), Green (1st), or Blue (2nd) coordinate of this
color via index access.
INPUT:
- ``i`` - an integer; the 0-based coordinate to retrieve
OUTPUT:
- a float
EXAMPLES::
sage: from sage.plot.colors import crimson, midnightblue
sage: Color('#badfad')[0]
0.7294117647058823
sage: (crimson[0], crimson[1], crimson[2]) == crimson.rgb()
True
sage: midnightblue[2] == midnightblue[-1]
True
sage: midnightblue[3]
Traceback (most recent call last):
...
IndexError: tuple index out of range
"""
return self.__rgb[i]
def rgb(self):
"""
Return the underlying Red-Green-Blue (RGB) coordinates of this
color.
OUTPUT:
- a 3-tuple of floats
EXAMPLES::
sage: Color(0.3, 0.5, 0.7).rgb()
(0.3, 0.5, 0.7)
sage: Color('#8000ff').rgb()
(0.5019607843137255, 0.0, 1.0)
sage: from sage.plot.colors import orange
sage: orange.rgb()
(1.0, 0.6470588235294118, 0.0)
sage: Color('magenta').rgb()
(1.0, 0.0, 1.0)
sage: Color(1, 0.7, 0.9, space='hsv').rgb()
(0.9, 0.2700000000000001, 0.2700000000000001)
"""
return self.__rgb
def hls(self):
"""
Return the Hue-Lightness-Saturation (HLS) coordinates of this
color.
OUTPUT:
- a 3-tuple of floats
EXAMPLES::
sage: Color(0.3, 0.5, 0.7, space='hls').hls()
(0.30000000000000004, 0.5, 0.7)
sage: Color(0.3, 0.5, 0.7, space='hsl').hls()
(0.30000000000000004, 0.7, 0.5000000000000001)
sage: Color('#aabbcc').hls()
(0.5833333333333334, 0.7333333333333334, 0.25000000000000017)
sage: from sage.plot.colors import orchid
sage: orchid.hls()
(0.8396226415094339, 0.6470588235294117, 0.5888888888888889)
"""
return tuple(map(float, rgb_to_hls(*self.__rgb)))
def hsl(self):
"""
Return the Hue-Saturation-Lightness (HSL) coordinates of this
color.
OUTPUT:
- a 3-tuple of floats
EXAMPLES::
sage: Color(1,0,0).hsl()
(0.0, 1.0, 0.5)
sage: from sage.plot.colors import orchid
sage: orchid.hsl()
(0.8396226415094339, 0.5888888888888889, 0.6470588235294117)
sage: Color('#aabbcc').hsl()
(0.5833333333333334, 0.25000000000000017, 0.7333333333333334)
"""
h, l, s = tuple(map(float, rgb_to_hls(*self.__rgb)))
return (h, s, l)
def hsv(self):
"""
Return the Hue-Saturation-Value (HSV) coordinates of this
color.
OUTPUT:
- a 3-tuple of floats
EXAMPLES::
sage: from sage.plot.colors import red
sage: red.hsv()
(0.0, 1.0, 1.0)
sage: Color(1,1,1).hsv()
(0.0, 0.0, 1.0)
sage: Color('gray').hsv()
(0.0, 0.0, 0.5019607843137255)
"""
return tuple(map(float, rgb_to_hsv(*self.__rgb)))
def html_color(self):
"""
Return a HTML hex representation for this color.
OUTPUT:
- a string of length 7.
EXAMPLES::
sage: Color('yellow').html_color()
'#ffff00'
sage: Color('#fedcba').html_color()
'#fedcba'
sage: Color(0.0, 1.0, 0.0).html_color()
'#00ff00'
sage: from sage.plot.colors import honeydew
sage: honeydew.html_color()
'#f0fff0'
"""
return float_to_html(*self.__rgb)
def lighter(self, fraction=1.0/3.0):
"""
Return a lighter "shade" of this RGB color by
:meth:`blend`-ing it with white. This is **not** an inverse
of :meth:`darker`.
INPUT:
- ``fraction`` - a float (default: 1.0/3.0); blending fraction
to apply
OUTPUT:
- a **new** instance of :class:`Color`
EXAMPLES::
sage: from sage.plot.colors import khaki
sage: khaki.lighter()
RGB color (0.9607843137254903, 0.934640522875817, 0.6993464052287582)
sage: Color('white').lighter().darker()
RGB color (0.6666666666666667, 0.6666666666666667, 0.6666666666666667)
sage: Color('#abcdef').lighter(1/4)
RGB color (0.7529411764705882, 0.8529411764705883, 0.9529411764705882)
sage: Color(1, 0, 8/9, space='hsv').lighter()
RGB color (0.925925925925926, 0.925925925925926, 0.925925925925926)
"""
return self.blend((1.0, 1.0, 1.0), fraction)
def darker(self, fraction=1.0/3.0):
"""
Return a darker "shade" of this RGB color by :meth:`blend`-ing
it with black. This is **not** an inverse of :meth:`lighter`.
INPUT:
- ``fraction`` - a float (default: 1.0/3.0); blending fraction
to apply
OUTPUT:
- a new instance of :class:`Color`
EXAMPLES::
sage: from sage.plot.colors import black
sage: vector(black.darker().rgb()) == vector(black.rgb())
True
sage: Color(0.4, 0.6, 0.8).darker(0.1)
RGB color (0.36000000000000004, 0.54, 0.7200000000000001)
sage: Color(.1,.2,.3,space='hsl').darker()
RGB color (0.24000000000000002, 0.20800000000000002, 0.16)
"""
return self.blend((0.0, 0.0, 0.0), fraction)
class ColorsDict(dict):
"""
A dict-like collection of colors, accessible via key or attribute.
For a list of color names, evaluate::
sage: sorted(colors)
['aliceblue', 'antiquewhite', 'aqua', 'aquamarine', ...]
"""
def __init__(self):
"""
Constructs a dict-like collection of colors. The keys are the
color names (i.e., strings) and the values are RGB 3-tuples of
floats.
EXAMPLES::
sage: from sage.plot.colors import ColorsDict
sage: cols = ColorsDict()
sage: set([(type(c), type(cols[c])) for c in cols])
set([(<type 'str'>, <class 'sage.plot.colors.Color'>)])
sage: sorted(cols)
['aliceblue', 'antiquewhite', 'aqua', 'aquamarine', ...]
sage: len(cols)
148
"""
for k in colors_dict:
self[k] = Color(colors_dict[k])
def __getattr__(self, name):
"""
Gets a color via attribute access.
INPUT:
- ``name`` - a string; the name of the color to return
OUTPUT:
- a RGB 3-tuple of floats
EXAMPLES::
sage: from sage.plot.colors import ColorsDict, blue
sage: cols = ColorsDict()
sage: cols.blue
RGB color (0.0, 0.0, 1.0)
sage: cols['blue']
RGB color (0.0, 0.0, 1.0)
sage: blue
RGB color (0.0, 0.0, 1.0)
sage: cols.punk
Traceback (most recent call last):
...
AttributeError: 'ColorsDict' has no attribute or colormap punk
"""
try:
return self.__getitem__(name)
except KeyError:
raise AttributeError("'%s' has no attribute or colormap %s"%(type(self).__name__,name))
def __dir__(self):
"""
Returns an approximate list of attribute names, including the
color names.
OUTPUT:
- a list of strings
EXAMPLES::
sage: from sage.plot.colors import ColorsDict
sage: cols = ColorsDict()
sage: 'green' in dir(cols)
True
"""
methods = ['__dir__', '__getattr__']
return dir(super(ColorsDict, self)) + methods + self.keys()
colors = ColorsDict()
for c in colors:
vars()[c] = colors[c]
def hue(h, s=1, v=1):
r"""
Convert a Hue-Saturation-Value (HSV) color tuple to a valid
Red-Green-Blue (RGB) tuple. All three inputs should lie in the
interval [0.0, 1.0]; otherwise, they are reduced modulo 1 (see
:func:`mod_one`). In particular ``h=0`` and ``h=1`` yield red,
with the intermediate hues orange, yellow, green, cyan, blue, and
violet as ``h`` increases.
This function makes it easy to sample a broad range of colors for
graphics::
sage: p = Graphics()
sage: for phi in xsrange(0, 2 * pi, 1 / pi):
... p += plot(sin(x + phi), (x, -7, 7), rgbcolor = hue(phi))
sage: p
INPUT:
- ``h`` - a number; the color's hue
- ``s`` - a number (default: 1); the color's saturation
- ``v`` - a number (default: 1); the color's value
OUTPUT:
- a RGB 3-tuple of floats in the interval [0.0, 1.0]
EXAMPLES::
sage: hue(0.6)
(0.0, 0.40000000000000036, 1.0)
sage: from sage.plot.colors import royalblue
sage: royalblue
RGB color (0.2549019607843137, 0.4117647058823529, 0.8823529411764706)
sage: hue(*royalblue.hsv())
(0.2549019607843137, 0.4117647058823529, 0.8823529411764706)
sage: hue(.5, .5, .5)
(0.25, 0.5, 0.5)
.. note :: The HSV to RGB coordinate transformation itself is
given in the source code for the Python library's
:mod:`colorsys` module::
sage: from colorsys import hsv_to_rgb # not tested
sage: hsv_to_rgb?? # not tested
"""
return tuple(map(float, hsv_to_rgb(mod_one(h), mod_one(s), mod_one(v))))
def float_to_html(r, g, b):
"""
Converts a Red-Green-Blue (RGB) color tuple to a HTML hex color.
Each input value should be in the interval [0.0, 1.0]; otherwise,
the values are first reduced modulo one (see :func:`mod_one`).
INPUT:
- ``r`` - a number; the RGB color's "red" intensity
- ``g`` - a number; the RGB color's "green" intensity
- ``b`` - a number; the RGB color's "blue" intensity
OUTPUT:
- a string of length 7, starting with '#'
EXAMPLES::
sage: from sage.plot.colors import float_to_html
sage: float_to_html(1.,1.,0.)
'#ffff00'
sage: float_to_html(.03,.06,.02)
'#070f05'
sage: float_to_html(*Color('brown').rgb())
'#a52a2a'
sage: float_to_html((0.2, 0.6, 0.8))
Traceback (most recent call last):
...
TypeError: float_to_html() takes exactly 3 arguments (1 given)
"""
from sage.rings.integer import Integer
from math import floor
r, g, b = map(mod_one, (r, g, b))
rr = Integer(int(floor(r * 255))).str(base = 16)
gg = Integer(int(floor(g * 255))).str(base = 16)
bb = Integer(int(floor(b * 255))).str(base = 16)
rr = '0' * (2 - len(rr)) + rr
gg = '0' * (2 - len(gg)) + gg
bb = '0' * (2 - len(bb)) + bb
return '#' + rr + gg + bb
def rainbow(n, format='hex'):
"""
Returns a list of colors sampled at equal intervals over the
spectrum, from Hue-Saturation-Value (HSV) coordinates (0, 1, 1) to
(1, 1, 1). This range is red at the extremes, but it covers
orange, yellow, green, cyan, blue, violet, and many other hues in
between. This function is particularly useful for representing
vertex partitions on graphs.
INPUT:
- ``n`` - a number; the length of the list
- ``format`` - a string (default: 'hex'); the output format for
each color in the list; the other choice is 'rgbtuple'
OUTPUT:
- a list of strings or RGB 3-tuples of floats in the interval
[0.0, 1.0]
EXAMPLES::
sage: from sage.plot.colors import rainbow
sage: rainbow(7)
['#ff0000', '#ffda00', '#48ff00', '#00ff91', '#0091ff', '#4800ff', '#ff00da']
sage: rainbow(7, 'rgbtuple')
[(1.0, 0.0, 0.0), (1.0, 0.8571428571428571, 0.0), (0.2857142857142858, 1.0, 0.0), (0.0, 1.0, 0.5714285714285712), (0.0, 0.5714285714285716, 1.0), (0.2857142857142847, 0.0, 1.0), (1.0, 0.0, 0.8571428571428577)]
AUTHORS:
- Robert L. Miller
- Karl-Dieter Crisman (directly use :func:`hsv_to_rgb` for hues)
"""
from sage.rings.integer import Integer
n = Integer(n)
R = []
for i in range(n):
R.append(tuple(map(float, hsv_to_rgb(i / n, 1, 1))))
if format == 'rgbtuple':
return R
elif format == 'hex':
for j in range(len(R)):
R[j] = float_to_html(*R[j])
return R
def get_cmap(cmap):
r"""
Returns a color map (actually, a matplotlib :class:`Colormap`
object), given its name or a [mixed] list/tuple of RGB list/tuples
and color names. For a list of map names, evaluate::
sage: sorted(colormaps)
['Accent', 'Accent_r', 'Blues', 'Blues_r', ...]
See :func:`rgbcolor` for valid list/tuple element formats.
INPUT:
- ``cmap`` - a string, list, tuple, or
:class:`matplotlib.colors.Colormap`; a string must be a valid
color map name
OUTPUT:
- a :class:`matplotlib.colors.Colormap` instance
EXAMPLES::
sage: from sage.plot.colors import get_cmap
sage: get_cmap('jet')
<matplotlib.colors.LinearSegmentedColormap instance at 0x...>
sage: get_cmap([(0,0,0), (0.5,0.5,0.5), (1,1,1)])
<matplotlib.colors.ListedColormap instance at 0x...>
sage: get_cmap(['green', 'lightblue', 'blue'])
<matplotlib.colors.ListedColormap instance at 0x...>
sage: get_cmap(((0.5, 0.3, 0.2), [1.0, 0.0, 0.5], 'purple', Color(0.5,0.5,1, space='hsv')))
<matplotlib.colors.ListedColormap instance at 0x...>
sage: get_cmap('jolies')
Traceback (most recent call last):
...
RuntimeError: Color map jolies not known (type import matplotlib.cm; matplotlib.cm.datad.keys() for valid names)
sage: get_cmap('mpl')
Traceback (most recent call last):
...
RuntimeError: Color map mpl not known (type import matplotlib.cm; matplotlib.cm.datad.keys() for valid names)
"""
global cm
if not cm:
from matplotlib import cm
from matplotlib.colors import ListedColormap, Colormap
if isinstance(cmap, Colormap):
return cmap
elif isinstance(cmap, str):
if not cmap in cm.datad.keys():
raise RuntimeError("Color map %s not known (type import matplotlib.cm; matplotlib.cm.datad.keys() for valid names)" % cmap)
return cm.__dict__[cmap]
elif isinstance(cmap, (list, tuple)):
cmap = map(rgbcolor, cmap)
return ListedColormap(cmap)
class Colormaps(collections.MutableMapping):
"""
A dict-like collection of lazily-loaded matplotlib color maps.
For a list of map names, evaluate::
sage: sorted(colormaps)
['Accent', 'Accent_r', 'Blues', 'Blues_r', ...]
"""
def __init__(self):
"""
Constructs an empty mutable collection of color maps.
EXAMPLES::
sage: from sage.plot.colors import Colormaps
sage: maps = Colormaps()
sage: len(maps.maps)
0
"""
self.maps = {}
def load_maps(self):
"""
If it's necessary, loads matplotlib's color maps and adds them
to the collection.
EXAMPLES::
sage: from sage.plot.colors import Colormaps
sage: maps = Colormaps()
sage: len(maps.maps)
0
sage: maps.load_maps()
sage: len(maps.maps)>130
True
"""
global cm
if not cm:
from matplotlib import cm
if not self.maps:
for cmap in cm.datad.keys():
self.maps[cmap] = cm.__getattribute__(cmap)
def __dir__(self):
"""
Returns an approximate list of attribute names, including the
color map names.
OUTPUT:
- a list of strings
EXAMPLES::
sage: from sage.plot.colors import Colormaps
sage: maps = Colormaps()
sage: 'Accent' in dir(maps)
True
"""
self.load_maps()
methods = ['load_maps', '__dir__', '__len__', '__iter__',
'__contains__', '__getitem__', '__getattr__',
'__setitem__', '__delitem__']
return dir(super(Colormaps, self)) + methods + self.keys()
def __len__(self):
"""
Returns the number of color maps.
OUTPUT:
- an int
EXAMPLES::
sage: from sage.plot.colors import Colormaps
sage: maps = Colormaps()
sage: len(maps)>130
True
"""
self.load_maps()
return len(self.maps)
def __iter__(self):
"""
Returns an iterator over the color map collection.
OUTPUT:
- a dictionary key iterator instance
EXAMPLES::
sage: from sage.plot.colors import Colormaps
sage: maps = Colormaps()
sage: count = 0
sage: for m in maps: count += 1
sage: count == len(maps)
True
"""
self.load_maps()
return iter(self.maps)
def __contains__(self, name):
"""
Returns whether a map is in the color maps collection.
INPUT:
- ``name`` - a string; the name of the map to query
OUTPUT:
- a boolean
EXAMPLES::
sage: from sage.plot.colors import Colormaps
sage: maps = Colormaps()
sage: 'summer' in maps
True
sage: 'not really a color map' in maps
False
"""
self.load_maps()
return name in self.maps
def __getitem__(self, name):
"""
Gets a color map from the collection via key access.
INPUT:
- ``name`` - a string; the name of the map return
OUTPUT:
- an instance of :class:`matplotlib.colors.Colormap`
EXAMPLES::
sage: from sage.plot.colors import Colormaps
sage: maps = Colormaps()
sage: maps.get('Oranges')
<matplotlib.colors.LinearSegmentedColormap instance at ...>
sage: maps['copper']
<matplotlib.colors.LinearSegmentedColormap instance at ...>
sage: maps.get('not a color map')
sage: maps['not a color map']
Traceback (most recent call last):
...
KeyError: "no colormap with name 'not a color map'"
"""
self.load_maps()
try:
return self.maps[name]
except KeyError:
raise KeyError("no colormap with name '%s'" % name)
def __getattr__(self, name):
"""
Gets a color map from the collection via attribute access.
INPUT:
- ``name`` - a string; the name of the map to return
OUTPUT:
- an instance of :class:`matplotlib.colors.Colormap`
EXAMPLES::
sage: from sage.plot.colors import Colormaps
sage: maps = Colormaps()
sage: maps.pink
<matplotlib.colors.LinearSegmentedColormap instance at ...>
sage: maps.punk
Traceback (most recent call last):
...
AttributeError: 'Colormaps' has no attribute or colormap punk
sage: maps['punk']
Traceback (most recent call last):
...
KeyError: "no colormap with name 'punk'"
sage: maps['bone'] == maps.bone
True
"""
try:
return self.__getitem__(name)
except KeyError:
raise AttributeError("'%s' has no attribute or colormap %s"%(type(self).__name__,name))
def __repr__(self):
"""
Returns a string representation of the color map collection.
OUTPUT:
- a string
EXAMPLES::
sage: from sage.plot.colors import Colormaps
sage: maps = Colormaps()
sage: maps
{...}
sage: type(repr(maps))
<type 'str'>
"""
self.load_maps()
return repr(self.maps)
def __setitem__(self, name, colormap):
"""
Adds a color map to the collection.
INPUT:
- ``name`` - a string; the name of the map to add
- ``colormap`` - an instance of
:class:`matplotlib.colors.Colormap`; the color map to add
EXAMPLES::
sage: from sage.plot.colors import Colormaps, get_cmap
sage: maps = Colormaps()
sage: count = len(maps)
sage: my_map = get_cmap(['chartreuse', '#007', (1.0, 0.0, 0.0)])
sage: maps['my_map'] = my_map
sage: 'my_map' in maps
True
sage: count + 1 == len(maps)
True
"""
self.load_maps()
self.maps[name] = colormap
def __delitem__(self, name):
"""
Removes a color map from the collection.
INPUT:
- ``name`` - a string; the name of the map to remove
EXAMPLES::
sage: from sage.plot.colors import Colormaps
sage: maps = Colormaps()
sage: count = len(maps)
sage: maps.popitem()
('Spectral', <matplotlib.colors.LinearSegmentedColormap instance at ...>)
sage: count - 1 == len(maps)
True
"""
self.load_maps()
del self.maps[name]
colormaps = Colormaps()