Skip to content

Commit aee7d1b

Browse files
committed
Color filtering cleanup, rename module-level variable
1 parent e6dc290 commit aee7d1b

File tree

3 files changed

+76
-69
lines changed

3 files changed

+76
-69
lines changed

CHANGELOG.rst

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,20 @@ ProPlot v0.2.4 (2019-12-07)
1919
===========================
2020
Deprecated
2121
----------
22-
- Rename `colordict` to `~proplot.styletools.colors`, remove `fonts_system` and
23-
`fonts_proplot`, make top-level variables more robust (:commit:`861583f8`).
22+
- Rename `ColorCacheDict` to `~proplot.styletools.ColorDict`.
23+
- Rename `colors` to `~proplot.styletools.Colors`.
24+
- Remove `fonts_system` and `fonts_proplot`, rename `colordict` to
25+
`~proplot.styletools.colors`, make top-level variables
26+
more robust (:commit:`861583f8`).
2427

2528
Documentation
2629
-------------
2730
- Params table for `~proplot.styletools.show_fonts` (:commit:`861583f8`).
2831

32+
Internals
33+
---------
34+
- Improvements to `~proplot.styletools.register_colors`.
35+
2936
ProPlot v0.2.3 (2019-12-05)
3037
===========================
3138
Bug fixes

docs/colorbars_legends.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,10 @@
133133
" share=0, wspace=0.3, order='F')\n",
134134
"data = (np.random.rand(50, 50) - 0.1).cumsum(axis=0)\n",
135135
"m = axs[:2].contourf(data, cmap='grays', extend='both')\n",
136-
"cycle = plot.colors('grays', 5)\n",
136+
"colors = plot.Colors('grays', 5)\n",
137137
"hs = []\n",
138138
"state = np.random.RandomState(51423)\n",
139-
"for abc, color in zip('ABCDEF', cycle):\n",
139+
"for abc, color in zip('ABCDEF', colors):\n",
140140
" h = axs[2:].plot(state.rand(10), lw=3, color=color, label=f'line {abc}')\n",
141141
" hs.extend(h[0])\n",
142142
"f.colorbar(m[0], length=0.8, label='colorbar label', loc='b', col=1, locator=5)\n",

proplot/styletools.py

Lines changed: 65 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,18 @@
2424
from . import colormath
2525
from .utils import _warn_proplot, _notNone, _timer
2626
__all__ = [
27-
'BinNorm', 'CmapDict', 'ColorCacheDict',
28-
'LinearSegmentedNorm', 'MidpointNorm', 'PerceptuallyUniformColormap',
29-
'Colormap', 'Cycle', 'Norm',
30-
'cmaps', 'cycles', 'colors', 'fonts',
31-
'ListedColormap',
27+
'BinNorm', 'CmapDict', 'ColorDict',
28+
'LinearSegmentedNorm',
3229
'LinearSegmentedColormap',
30+
'ListedColormap',
31+
'MidpointNorm', 'PerceptuallyUniformColormap',
32+
'cmaps', 'colors', 'cycles', 'fonts',
3333
'make_mapping_array',
3434
'register_cmaps', 'register_colors', 'register_cycles', 'register_fonts',
3535
'saturate', 'shade', 'show_cmaps', 'show_channels',
3636
'show_colors', 'show_colorspaces', 'show_cycles', 'show_fonts',
3737
'to_rgb', 'to_xyz',
38+
'Colormap', 'Colors', 'Cycle', 'Norm',
3839
]
3940

4041
# Colormap stuff
@@ -370,7 +371,7 @@ def to_rgb(color, space='rgb', cycle=None, alpha=False):
370371
color : str or length-3 list
371372
The color specification. Can be a tuple of channel values for the
372373
`space` colorspace, a hex string, a registered color name, a cycle
373-
color, or a colormap color (see `ColorCacheDict`).
374+
color, or a colormap color (see `ColorDict`).
374375
375376
If `space` is ``'rgb'``, this is a tuple of RGB values, and any
376377
channels are larger than ``2``, the channels are assumed to be on
@@ -1749,6 +1750,15 @@ def __init__(self, kwargs):
17491750
for record in (cmaps, cycles):
17501751
record[:] = sorted(record)
17511752

1753+
def __delitem__(self, key):
1754+
"""Delete the item from the list records."""
1755+
super().__delitem__(self, key)
1756+
for record in (cmaps, cycles):
1757+
try:
1758+
record.remove(key)
1759+
except ValueError:
1760+
pass
1761+
17521762
def __getitem__(self, key):
17531763
"""Retrieve the case-insensitive colormap name. If the name ends in
17541764
``'_r'``, returns the result of ``cmap.reversed()`` for the colormap
@@ -1852,17 +1862,17 @@ def update(self, *args, **kwargs):
18521862

18531863

18541864
class _ColorMappingOverride(mcolors._ColorMapping):
1855-
"""Mapping whose cache attribute is a `ColorCacheDict` dictionary."""
1865+
"""Mapping whose cache attribute is a `ColorDict` dictionary."""
18561866
def __init__(self, mapping):
18571867
super().__init__(mapping)
1858-
self.cache = ColorCacheDict({})
1868+
self.cache = ColorDict({})
18591869

18601870

1861-
class ColorCacheDict(dict):
1871+
class ColorDict(dict):
18621872
"""This class overrides the builtin matplotlib color cache, allowing
18631873
users to draw colors from *named colormaps and color cycles* for any
18641874
plotting command that accepts a `color` keyword arg.
1865-
See `~ColorCacheDict.__getitem__` for details."""
1875+
See `~ColorDict.__getitem__` for details."""
18661876
def __getitem__(self, key):
18671877
"""
18681878
Allows user to select colors from arbitrary named colormaps and
@@ -1908,7 +1918,7 @@ def __getitem__(self, key):
19081918
return super().__getitem__((rgb, alpha))
19091919

19101920

1911-
def colors(*args, **kwargs):
1921+
def Colors(*args, **kwargs):
19121922
"""Identical to `Cycle`, but returns a list of colors instead of
19131923
a `~cycler.Cycler` object."""
19141924
cycle = Cycle(*args, **kwargs)
@@ -2383,9 +2393,7 @@ class BinNorm(mcolors.BoundaryNorm):
23832393
is determined with `numpy.searchsorted`, and its corresponding
23842394
colormap coordinate is selected using this index.
23852395
2386-
The input parameters are as follows.
23872396
"""
2388-
23892397
def __init__(self, levels, norm=None, clip=False,
23902398
step=1.0, extend='neither'):
23912399
"""
@@ -2903,76 +2911,70 @@ def register_colors(nmax=np.inf):
29032911
# Reset native colors dictionary and add some default groups
29042912
# Add in CSS4 so no surprises for user, but we will not encourage this
29052913
# usage and will omit CSS4 colors from the demo table.
2906-
base = {}
29072914
colors.clear()
2915+
base = {}
29082916
base.update(mcolors.BASE_COLORS)
29092917
base.update(BASE_COLORS_FULL)
29102918
mcolors.colorConverter.colors.clear() # clean out!
29112919
mcolors.colorConverter.cache.clear() # clean out!
29122920
for name, dict_ in (('base', base), ('css', mcolors.CSS4_COLORS)):
2913-
colors.update({name: dict_})
2921+
mcolors.colorConverter.colors.update(dict_)
2922+
colors[name] = sorted(dict_)
29142923

29152924
# Load colors from file and get their HCL values
2916-
# TODO: Cleanup!
2925+
dicts = {}
29172926
seen = {*base} # never overwrite base names, e.g. 'blue' and 'b'!
2918-
hcls = np.empty((0, 3))
2919-
pairs = []
2927+
hcls = []
2928+
data = []
29202929
for path in _get_data_paths('colors'):
29212930
# prefer xkcd
29222931
for file in sorted(glob.glob(os.path.join(path, '*.txt')))[::-1]:
29232932
cat, _ = os.path.splitext(os.path.basename(file))
29242933
with open(file, 'r') as f:
2925-
data = [tuple(item.strip() for item in line.split(':'))
2926-
for line in f.readlines() if line.strip()]
2927-
if not all(len(pair) == 2 for pair in data):
2934+
pairs = [tuple(item.strip() for item in line.split(':'))
2935+
for line in f.readlines() if line.strip()]
2936+
if not all(len(pair) == 2 for pair in pairs):
29282937
raise RuntimeError(
29292938
f'Invalid color names file {file!r}. '
29302939
f'Every line must be formatted as "name: color".')
2931-
# Immediately add all open colors
2940+
2941+
# Add all open colors
29322942
if cat == 'open':
2933-
dict_ = {name: color for name, color in data}
2934-
colors.update({'open': dict_})
2943+
dict_ = {name: color for name, color in pairs}
2944+
mcolors.colorConverter.colors.update(dict_)
2945+
colors['open'] = sorted(dict_)
29352946
continue
2936-
# Remaining dicts are filtered and their names are sanitized
2937-
i = 0
2938-
dict_ = {}
2939-
ihcls = []
2940-
colors[cat] = {} # just initialize this one
2941-
for name, color in data: # is list of name, color tuples
2942-
if i >= nmax: # e.g. for xkcd colors
2947+
2948+
# Filter remaining colors to unique ones
2949+
j = 0
2950+
if cat not in dicts:
2951+
dicts[cat] = {}
2952+
for name, color in pairs: # is list of name, color tuples
2953+
j += 1
2954+
if j > nmax: # e.g. for xkcd colors
29432955
break
29442956
for regex, sub in FILTER_TRANSLATIONS:
29452957
name = regex.sub(sub, name)
29462958
if name in seen or FILTER_BAD.search(name):
29472959
continue
29482960
seen.add(name)
2949-
pairs.append((cat, name)) # save the category name pair
2950-
ihcls.append(to_xyz(color, space=FILTER_SPACE))
2951-
dict_[name] = color # save the color
2952-
i += 1
2953-
_colors_unfiltered[cat] = dict_
2954-
hcls = np.concatenate((hcls, ihcls), axis=0)
2961+
hcls.append(to_xyz(color, space=FILTER_SPACE))
2962+
data.append((cat, name, color)) # category name pair
29552963

29562964
# Remove colors that are 'too similar' by rounding to the nearest n units
29572965
# WARNING: Unique axis argument requires numpy version >=1.13
2958-
scale = (360, 100, 100)
2966+
hcls = np.array(hcls)
29592967
if hcls.size > 0:
2960-
hcls = hcls / np.array(scale)
2968+
hcls = hcls / np.array([360, 100, 100])
29612969
hcls = np.round(hcls / FILTER_THRESH).astype(np.int64)
2962-
deleted = 0
2963-
_, idxs, _ = np.unique(hcls,
2964-
return_index=True,
2965-
return_counts=True,
2966-
axis=0) # get unique rows
2967-
for idx, (cat, name) in enumerate(pairs):
2968-
if name not in FILTER_ADD and idx not in idxs:
2969-
deleted += 1
2970-
else:
2971-
colors[cat][name] = _colors_unfiltered[cat][name]
2972-
2973-
# Update the color converter
2974-
for _, kw in colors.items():
2975-
mcolors.colorConverter.colors.update(kw)
2970+
_, idxs, _ = np.unique(
2971+
hcls, return_index=True, return_counts=True, axis=0)
2972+
for idx, (cat, name, color) in enumerate(data):
2973+
if name in FILTER_ADD or idx in idxs:
2974+
dicts[cat][name] = color
2975+
for cat, dict_ in dicts.items():
2976+
mcolors.colorConverter.colors.update(dict_)
2977+
colors[cat] = sorted(dict_)
29762978

29772979

29782980
@_timer
@@ -3279,12 +3281,13 @@ def show_colors(nbreak=17, minsat=0.2):
32793281
for open_colors in (True, False):
32803282
scale = (360, 100, 100)
32813283
if open_colors:
3282-
group = ['open']
3284+
cats = ['open']
32833285
else:
3284-
group = [name for name in colors if name not in ('css', 'open')]
3285-
icolors = {}
3286-
for name in group:
3287-
icolors.update(colors[name]) # add category dictionary
3286+
cats = [name for name in colors if name not in ('css', 'open')]
3287+
data = {}
3288+
for cat in cats:
3289+
for color in colors[cat]:
3290+
data[color] = mcolors.colorConverter.colors[color]
32883291

32893292
# Group colors together by discrete range of hue, then sort by value
32903293
# For opencolors this is not necessary
@@ -3314,7 +3317,7 @@ def show_colors(nbreak=17, minsat=0.2):
33143317
value,
33153318
FILTER_SPACE),
33163319
scale)]
3317-
for key, value in icolors.items()
3320+
for key, value in data.items()
33183321
}
33193322
# Separate into columns and roughly sort by brightness in columns
33203323
# group in blocks of 20 hues
@@ -3372,7 +3375,7 @@ def show_colors(nbreak=17, minsat=0.2):
33723375
xi_text = wsep * (col + 0.25 * swatch + 0.03 * swatch)
33733376
ax.text(xi_text, y, name, ha='left', va='center')
33743377
ax.hlines(y_line, xi_line, xf_line,
3375-
color=icolors[name], lw=hsep * 0.6)
3378+
color=data[name], lw=hsep * 0.6)
33763379
# Apply formatting
33773380
ax.format(xlim=(0, X), ylim=(0, Y))
33783381
ax.set_axis_off()
@@ -3582,11 +3585,8 @@ def show_fonts(*args, size=12):
35823585
#: List of registered color cycle names.
35833586
cycles = [] # track *all* color cycles
35843587

3585-
#: Registered color names by category.
3586-
colors = {} # 'sufficiently unique' color names # noqa
3587-
3588-
#: All color names by category
3589-
_colors_unfiltered = {}
3588+
#: Lists of registered color names by category.
3589+
colors = {}
35903590

35913591
#: Registered font names.
35923592
fonts = []

0 commit comments

Comments
 (0)