Skip to content

Commit 7998c60

Browse files
committed
simple wrapper on boundarynorm + listed colormap
1 parent 12cafa9 commit 7998c60

6 files changed

Lines changed: 230 additions & 227 deletions

File tree

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ install:
110110
# Install nose from a build which has partial
111111
# support for python36 and suport for coverage output suppressing
112112
pip install git+https://github.com/jenshnielsen/nose.git@matplotlibnose
113-
113+
pip install pytest
114114
# We manually install humor sans using the package from Ubuntu 14.10. Unfortunatly humor sans is not
115115
# availible in the Ubuntu version used by Travis but we can manually install the deb from a later
116116
# version since is it basically just a .ttf file

appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ install:
8282
# same things as the requirements in ci/conda_recipe/meta.yaml
8383
# if conda-forge gets a new pyqt, it might be nice to install it as well to have more backends
8484
# https://github.com/conda-forge/conda-forge.github.io/issues/157#issuecomment-223536381
85-
- cmd: conda create -q -n test-environment python=%PYTHON_VERSION% pip setuptools numpy python-dateutil freetype=2.6 msinttypes "tk=8.5" pyparsing pytz tornado "libpng>=1.6.21,<1.7" "zlib=1.2" "cycler>=0.10" nose mock
85+
- cmd: conda create -q -n test-environment python=%PYTHON_VERSION% pip setuptools numpy python-dateutil freetype=2.6 msinttypes "tk=8.5" pyparsing pytz tornado "libpng>=1.6.21,<1.7" "zlib=1.2" "cycler>=0.10" nose mock pytest
8686
- activate test-environment
8787
- cmd: echo %PYTHON_VERSION% %TARGET_ARCH%
8888
- cmd: IF %PYTHON_VERSION% == 2.7 conda install -q functools32

lib/matplotlib/axis.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ def __init__(self, axes, pickradius=15):
642642
self.offsetText = self._get_offset_text()
643643
self.majorTicks = []
644644
self.minorTicks = []
645-
self.unit_data = []
645+
self.unit_data = None
646646
self.pickradius = pickradius
647647

648648
# Initialize here for testing; later add API
@@ -695,14 +695,14 @@ def limit_range_for_scale(self, vmin, vmax):
695695

696696
@property
697697
def unit_data(self):
698-
"""Holds data that a ConversionInterface subclass relys on
698+
"""Holds data that a ConversionInterface subclass uses
699699
to convert between labels and indexes
700700
"""
701701
return self._unit_data
702702

703703
@unit_data.setter
704-
def unit_data(self, data):
705-
self._unit_data = data
704+
def unit_data(self, unit_data):
705+
self._unit_data = unit_data
706706

707707
def get_children(self):
708708
children = [self.label, self.offsetText]

lib/matplotlib/category.py

Lines changed: 79 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
"""
55
from __future__ import (absolute_import, division, print_function,
66
unicode_literals)
7-
87
import six
98

109
import numpy as np
1110

11+
import matplotlib.cbook as cbook
12+
import matplotlib.colors as mcolors
1213
import matplotlib.units as units
1314
import matplotlib.ticker as ticker
1415

@@ -22,10 +23,12 @@
2223
def to_array(data, maxlen=100):
2324
if NP_NEW:
2425
return np.array(data, dtype=np.unicode)
26+
if cbook.is_scalar_or_string(data):
27+
data = [data]
2528
try:
2629
vals = np.array(data, dtype=('|S', maxlen))
2730
except UnicodeEncodeError:
28-
# pure hack
31+
# this yields gibberish
2932
vals = np.array([convert_to_string(d) for d in data])
3033
return vals
3134

@@ -36,49 +39,83 @@ def convert(value, unit, axis):
3639
"""Uses axis.unit_data map to encode
3740
data as floats
3841
"""
39-
vmap = dict(axis.unit_data)
42+
vmap = dict(zip(axis.unit_data.seq, axis.unit_data.locs))
4043

4144
if isinstance(value, six.string_types):
4245
return vmap[value]
4346

4447
vals = to_array(value)
45-
for lab, loc in axis.unit_data:
48+
for lab, loc in vmap.items():
4649
vals[vals == lab] = loc
4750

4851
return vals.astype('float')
4952

5053
@staticmethod
5154
def axisinfo(unit, axis):
52-
seq, locs = zip(*axis.unit_data)
53-
majloc = StrCategoryLocator(locs)
54-
majfmt = StrCategoryFormatter(seq)
55+
majloc = StrCategoryLocator(axis.unit_data.locs)
56+
majfmt = StrCategoryFormatter(axis.unit_data.seq)
5557
return units.AxisInfo(majloc=majloc, majfmt=majfmt)
5658

5759
@staticmethod
5860
def default_units(data, axis):
5961
# the conversion call stack is:
6062
# default_units->axis_info->convert
61-
axis.unit_data = map_categories(data, axis.unit_data)
63+
if axis.unit_data is None:
64+
axis.unit_data = UnitData(data)
65+
else:
66+
axis.unit_data.update(data)
6267
return None
6368

6469

6570
class StrCategoryLocator(ticker.FixedLocator):
6671
def __init__(self, locs):
67-
super(StrCategoryLocator, self).__init__(locs, None)
72+
self.locs = locs
73+
self.nbins = None
6874

6975

7076
class StrCategoryFormatter(ticker.FixedFormatter):
7177
def __init__(self, seq):
72-
super(StrCategoryFormatter, self).__init__(seq)
78+
self.seq = seq
79+
self.offset_string = ''
80+
81+
82+
def colors_from_categories(codings):
83+
"""
84+
A helper routine to generate a cmap and a norm instance where
85+
a given key in coding is associated with a color value in coding
86+
87+
Parameters
88+
----------
89+
coding : sequence of [(key, value)] pairs where key is the
90+
categorical variable, and value is its associated
91+
color
92+
93+
Returns
94+
-------
95+
(cmap, norm) : tuple containing a :class:`Colormap` and a \
96+
:class:`Normalize` instance
97+
"""
98+
if isinstance(codings, dict):
99+
codings = codings.items()
100+
if six.PY3:
101+
codings = list(codings)
102+
103+
codings.sort()
104+
105+
cats, cols = zip(*codings)
106+
cmap = mcolors.ListedColormap(cols)
107+
cats = list(cats) + [np.inf]
108+
norm = mcolors.BoundaryNorm(cats, cmap.N)
109+
return cmap, norm
73110

74111

75112
def convert_to_string(value):
76113
"""Helper function for numpy 1.6, can be replaced with
77114
np.array(...,dtype=unicode) for all later versions of numpy"""
78115

79116
if isinstance(value, six.string_types):
80-
return value
81-
if np.isfinite(value):
117+
pass
118+
elif np.isfinite(value):
82119
value = np.asarray(value, dtype=str)[np.newaxis][0]
83120
elif np.isnan(value):
84121
value = 'nan'
@@ -91,59 +128,38 @@ def convert_to_string(value):
91128
return value
92129

93130

94-
def map_categories(data, old_map=None):
95-
"""Create mapping between unique categorical
96-
values and numerical identifier.
97-
98-
Paramters
99-
---------
100-
data: iterable
101-
sequence of values
102-
old_map: list of tuple, optional
103-
if not `None`, than old_mapping will be updated with new values and
104-
previous mappings will remain unchanged)
105-
sort: bool, optional
106-
sort keys by ASCII value
107-
108-
Returns
109-
-------
110-
list of tuple
111-
[(label, ticklocation),...]
112-
113-
"""
114-
115-
# code typical missing data in the negative range because
116-
# everything else will always have positive encoding
117-
# question able if it even makes sense
131+
class UnitData(object):
132+
# debatable makes sense to special code missing values
118133
spdict = {'nan': -1.0, 'inf': -2.0, '-inf': -3.0}
119134

120-
if isinstance(data, six.string_types):
121-
data = [data]
122-
123-
# will update this post cbook/dict support
124-
strdata = to_array(data)
125-
uniq = np.unique(strdata)
126-
127-
if old_map:
128-
olabs, okeys = zip(*old_map)
129-
svalue = max(okeys) + 1
130-
else:
131-
old_map, olabs, okeys = [], [], []
132-
svalue = 0
133-
134-
category_map = old_map[:]
135-
136-
new_labs = [u for u in uniq if u not in olabs]
137-
missing = [nl for nl in new_labs if nl in spdict.keys()]
138-
139-
category_map.extend([(m, spdict[m]) for m in missing])
140-
141-
new_labs = [nl for nl in new_labs if nl not in missing]
142-
143-
new_locs = np.arange(svalue, svalue + len(new_labs), dtype='float')
144-
category_map.extend(list(zip(new_labs, new_locs)))
145-
return category_map
146-
135+
def __init__(self, data):
136+
"""Create mapping between unique categorical values
137+
and numerical identifier
138+
Paramters
139+
---------
140+
data: iterable
141+
sequence of values
142+
"""
143+
self.seq, self.locs = [], []
144+
self._set_seq_locs(data, 0)
145+
146+
def update(self, new_data):
147+
# so as not to conflict with spdict
148+
value = max(max(self.locs) + 1, 0)
149+
self._set_seq_locs(new_data, value)
150+
151+
def _set_seq_locs(self, data, value):
152+
# magic to make it work under np1.6
153+
strdata = to_array(data)
154+
# np.unique makes dateframes work
155+
new_s = [d for d in np.unique(strdata) if d not in self.seq]
156+
for ns in new_s:
157+
self.seq.append(convert_to_string(ns))
158+
if ns in UnitData.spdict.keys():
159+
self.locs.append(UnitData.spdict[ns])
160+
else:
161+
self.locs.append(value)
162+
value += 1
147163

148164
# Connects the convertor to matplotlib
149165
units.registry[str] = StrCategoryConverter()

0 commit comments

Comments
 (0)