8000 Mpl traitlets by tacaswell · Pull Request #4694 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Mpl traitlets #4694

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 134 additions & 5 deletions lib/matplotlib/mpl_traitlets.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
from __future__ import (absolute_import, division,
print_function, unicode_literals)

from traitlets.config import (Configurable, TraitType) as (ipyConfigurbale, ipyTraitType)
#from traitlets.config import Configurable
#from traitlets import (Int, Float, Bool, Dict, List, Instance,
# Union, TraitError, HasTraits,
# NoDefaultSpecified, TraitType)

from traitlets import (Int, Float, Bool, Dict, List, Instance,
Union, TraitError, HasTraits, NoDefaultSpecified)
from IPython.config import Configurable
from IPython.utils.traitlets import (Int, Float, Bool, Dict, List, Instance,
Union, TraitError, HasTraits,
NoDefaultSpecified, TraitType)
import numpy as np

class Configurable(ipyConfigurable): pass
class Configurable(Configurable): pass

class TraitType(TraitType): pass

class OverloadMixin(object):

Expand Down Expand Up @@ -51,4 +59,125 @@ def validate(self, obj, value):
else:
self.error(obj, value)

class oInstance(OverloadMixin,Instance): pass
class oInstance(OverloadMixin,Instance): pass

class Color(TraitType):
"""A trait representing a color, can be either in RGB, or RGBA format.

Arguments:
force_rgb: bool: Force the return in RGB format instead of RGB. Default: False
as_hex: bool: Return the hex value instead. Default: False
default_alpha: float (0.0-1.0) or integer (0-255) default alpha value.

Accepts:
string: a valid hex color string (i.e. #FFFFFF). 7 chars
tuple: a tuple of ints (0-255), or tuple of floats (0.0-1.0)
float: A gray shade (0-1)
integer: A gray shade (0-255)

Defaults: RGBA tuple, color black (0.0, 0.0, 0.0, 0.0)

Return:
A hex color string, a rgb or a rgba tuple. Defaults to rgba. When
returning hex string, the alpha property will be ignored. A warning
will be emitted if alpha information is passed different then 0.0

"""
metadata = {
'force_rgb': False,
'as_hex' : False,
'default_alpha' : 0.0,
}
allow_none = False
info_text = 'float, int, tuple of float or int, or a hex string color'
default_value = (0.0,0.0,0.0,0.0)
named_colors = {}

def _int_to_float(self, value):
as_float = (np.array(value)/255).tolist()
return as_float

def _float_to_hex(self, value):
as_hex = '#%02x%02x%02x' % tuple([int(np.round(v * 255)) for v in\
value[:3]])
return as_hex

def _int_to_hex(self, value):
as_hex = '#%02x%02x%02x' % value[:3]
return as_hex

def _hex_to_float(self, value):
# Expects #FFFFFF format
split_hex = (value[1:3],value[3:5],value[5:7])
as_float = (np.array([int(v,16) for v in split_hex])/255.0).tolist()
return as_float

def _is_hex16(self, value):
try:
int(value, 16)
return True
except:
return False

def _float_to_shade(self, value):
grade = value*255.0
return (grade,grade,grade)

def _int_to_shade(self, value):
grade = value/255.0
return (grade,grade,grade)

def validate(self, obj, value):
in_range = False
if value is None or value is False or value in ['none','']:
# Return transparent if no other default alpha was set
return (0.0, 0.0, 0.0, 1.0)

if isinstance(value, float) and 0 <= value <= 1:
value = self._float_to_shade(value)
else:
in_range = False

if isinstance(value, int) and 0 <= value <= 255:
value = self._int_to_shade(value)
else:
in_range = False

if isinstance(value, (tuple, list)) and len(value) in (3,4):
is_all_float = np.prod([isinstance(v, (float)) for v in value])
in_range = np.prod([(0 <= v <= 1) for v in value])
if is_all_float and in_range:
value = value
else:
is_all_int = np.prod([isinstance(v, int) for v in value])
in_range = np.prod([(0 <= v <= 255) for v in value])
if is_all_int and in_range:
value = self._int_to_float(value)

if isinstance(value, str) and len(value) == 7 and value[0] == '#':
is_all_hex16 = np.prod([self._is_hex16(v) for v in\
(value[1:3],value[3:5],value[5:7])])
if is_all_hex16:
value = self._hex_to_float(value)
in_range = np.prod([(0 <= v <= 1) for v in value])
if in_range:
value = value

elif isinstance(value, str) and value in self.named_colors:
value = self.validate(obj, self.named_colors[value])
in_range = True

if in_range:
if self._metadata['as_hex']:
return self._float_to_hex(value)
if self._metadata['force_rgb'] and in_range:
return tuple(np.round(value[:3],5).tolist())
else:
if len(value) == 3:
value = tuple(np.round((value[0], value[1], value[2],
self._metadata['default_alpha']),5).tolist())
elif len(value) == 4:
value = tuple(np.round(value,5).tolist())
return value

self.error(obj, value)
109 changes: 109 additions & 0 deletions lib/matplotlib/tests/test_traitlets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from __future__ import absolute_import

from nose.tools import *
from unittest import TestCase
from matplotlib.mpl_traitlets import Color, HasTraits

class ColorTestCase(TestCase):
"""Tests for the Color traits"""

def setUp(self):
self.transparent_values = [None, False, '', 'none']
self.black_values = ['#000000', (0,0,0,0), 0, 0.0, (.0,.0,.0), (.0,.0,.0,.0)]
self.colored_values = ['#BE3537', (190,53,55), (0.7451, 0.20784, 0.21569)]
self.unvalid_values = ['áfaef', '#FFF', '#0SX#$S', (0,0,0), (0.45,0.3), (()), {}, True]

def _evaluate_unvalids(self, a):
for values in self.unvalid_values:
try:
a.color = values
except:
assert_raises(TypeError)

def test_noargs(self):
class A(HasTraits):
color = Color()
a = A()
for values in self.black_values:
a.color = values
assert_equal(a.color, (0.0,0.0,0.0,0.0))

for values in self.colored_values:
a.color = values
assert_equal(a.color, (0.7451, 0.20784, 0.21569, 0.0))
self._evaluate_unvalids(a)


def test_hexcolor(self):
class A(HasTraits):
color = Color(as_hex=True)

a = A()

for values in self.black_values:
a.color = values
assert_equal(a.color, '#000000')

for values in self.colored_values:
a.color = values
assert_equal(a.color, '#be3537')

self._evaluate_unvalids(a)

def test_rgb(self):
class A(HasTraits):
color = Color(force_rgb=True)

a = A()

for values in self.black_values:
a.color = values
assert_equal(a.color, (0.0,0.0,0.0))

for values in self.colored_values:
a.color = values
assert_equal(a.color, (0.7451, 0.20784, 0.21569))

self._evaluate_unvalids(a)

def test_named(self):
ncolors = {'hexblue': '#0000FF',
'floatbllue': (0.0,0.0,1.0),
'intblue' : (0,0,255)}

class A(HasTraits):
color = Color()
color.named_colors = ncolors

a = A()

for colorname in ncolors:
a.color = colorname
assert_equal(a.color, (0.0,0.0,1.0,0.0))

def test_alpha(self):
class A(HasTraits):
color = Color(default_alpha=0.4)

a = A()

assert_equal(a.color, (0.0, 0.0, 0.0, 0.0))

for values in self.transparent_values:
a.color = values
assert_equal(a.color, (0.0,0.0,0.0,1.0))

for values in self.black_values:
a.color = values
if isinstance(values, (tuple,list)) and len(values) == 4:
assert_equal(a.color, (0.0,0.0,0.0,0.0))
else:
assert_equal(a.color, (0.0,0.0,0.0,0.4))

for values in self.colored_values:
a.color = values
assert_equal(a.color, (0.7451, 0.20784, 0.21569, 0.4))

if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
0