|
24 | 24 | from . import colormath |
25 | 25 | from .utils import _warn_proplot, _notNone, _timer |
26 | 26 | __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', |
32 | 29 | 'LinearSegmentedColormap', |
| 30 | + 'ListedColormap', |
| 31 | + 'MidpointNorm', 'PerceptuallyUniformColormap', |
| 32 | + 'cmaps', 'colors', 'cycles', 'fonts', |
33 | 33 | 'make_mapping_array', |
34 | 34 | 'register_cmaps', 'register_colors', 'register_cycles', 'register_fonts', |
35 | 35 | 'saturate', 'shade', 'show_cmaps', 'show_channels', |
36 | 36 | 'show_colors', 'show_colorspaces', 'show_cycles', 'show_fonts', |
37 | 37 | 'to_rgb', 'to_xyz', |
| 38 | + 'Colormap', 'Colors', 'Cycle', 'Norm', |
38 | 39 | ] |
39 | 40 |
|
40 | 41 | # Colormap stuff |
@@ -370,7 +371,7 @@ def to_rgb(color, space='rgb', cycle=None, alpha=False): |
370 | 371 | color : str or length-3 list |
371 | 372 | The color specification. Can be a tuple of channel values for the |
372 | 373 | `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`). |
374 | 375 |
|
375 | 376 | If `space` is ``'rgb'``, this is a tuple of RGB values, and any |
376 | 377 | channels are larger than ``2``, the channels are assumed to be on |
@@ -1749,6 +1750,15 @@ def __init__(self, kwargs): |
1749 | 1750 | for record in (cmaps, cycles): |
1750 | 1751 | record[:] = sorted(record) |
1751 | 1752 |
|
| 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 | + |
1752 | 1762 | def __getitem__(self, key): |
1753 | 1763 | """Retrieve the case-insensitive colormap name. If the name ends in |
1754 | 1764 | ``'_r'``, returns the result of ``cmap.reversed()`` for the colormap |
@@ -1852,17 +1862,17 @@ def update(self, *args, **kwargs): |
1852 | 1862 |
|
1853 | 1863 |
|
1854 | 1864 | class _ColorMappingOverride(mcolors._ColorMapping): |
1855 | | - """Mapping whose cache attribute is a `ColorCacheDict` dictionary.""" |
| 1865 | + """Mapping whose cache attribute is a `ColorDict` dictionary.""" |
1856 | 1866 | def __init__(self, mapping): |
1857 | 1867 | super().__init__(mapping) |
1858 | | - self.cache = ColorCacheDict({}) |
| 1868 | + self.cache = ColorDict({}) |
1859 | 1869 |
|
1860 | 1870 |
|
1861 | | -class ColorCacheDict(dict): |
| 1871 | +class ColorDict(dict): |
1862 | 1872 | """This class overrides the builtin matplotlib color cache, allowing |
1863 | 1873 | users to draw colors from *named colormaps and color cycles* for any |
1864 | 1874 | plotting command that accepts a `color` keyword arg. |
1865 | | - See `~ColorCacheDict.__getitem__` for details.""" |
| 1875 | + See `~ColorDict.__getitem__` for details.""" |
1866 | 1876 | def __getitem__(self, key): |
1867 | 1877 | """ |
1868 | 1878 | Allows user to select colors from arbitrary named colormaps and |
@@ -1908,7 +1918,7 @@ def __getitem__(self, key): |
1908 | 1918 | return super().__getitem__((rgb, alpha)) |
1909 | 1919 |
|
1910 | 1920 |
|
1911 | | -def colors(*args, **kwargs): |
| 1921 | +def Colors(*args, **kwargs): |
1912 | 1922 | """Identical to `Cycle`, but returns a list of colors instead of |
1913 | 1923 | a `~cycler.Cycler` object.""" |
1914 | 1924 | cycle = Cycle(*args, **kwargs) |
@@ -2383,9 +2393,7 @@ class BinNorm(mcolors.BoundaryNorm): |
2383 | 2393 | is determined with `numpy.searchsorted`, and its corresponding |
2384 | 2394 | colormap coordinate is selected using this index. |
2385 | 2395 |
|
2386 | | - The input parameters are as follows. |
2387 | 2396 | """ |
2388 | | - |
2389 | 2397 | def __init__(self, levels, norm=None, clip=False, |
2390 | 2398 | step=1.0, extend='neither'): |
2391 | 2399 | """ |
@@ -2903,76 +2911,70 @@ def register_colors(nmax=np.inf): |
2903 | 2911 | # Reset native colors dictionary and add some default groups |
2904 | 2912 | # Add in CSS4 so no surprises for user, but we will not encourage this |
2905 | 2913 | # usage and will omit CSS4 colors from the demo table. |
2906 | | - base = {} |
2907 | 2914 | colors.clear() |
| 2915 | + base = {} |
2908 | 2916 | base.update(mcolors.BASE_COLORS) |
2909 | 2917 | base.update(BASE_COLORS_FULL) |
2910 | 2918 | mcolors.colorConverter.colors.clear() # clean out! |
2911 | 2919 | mcolors.colorConverter.cache.clear() # clean out! |
2912 | 2920 | 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_) |
2914 | 2923 |
|
2915 | 2924 | # Load colors from file and get their HCL values |
2916 | | - # TODO: Cleanup! |
| 2925 | + dicts = {} |
2917 | 2926 | seen = {*base} # never overwrite base names, e.g. 'blue' and 'b'! |
2918 | | - hcls = np.empty((0, 3)) |
2919 | | - pairs = [] |
| 2927 | + hcls = [] |
| 2928 | + data = [] |
2920 | 2929 | for path in _get_data_paths('colors'): |
2921 | 2930 | # prefer xkcd |
2922 | 2931 | for file in sorted(glob.glob(os.path.join(path, '*.txt')))[::-1]: |
2923 | 2932 | cat, _ = os.path.splitext(os.path.basename(file)) |
2924 | 2933 | 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): |
2928 | 2937 | raise RuntimeError( |
2929 | 2938 | f'Invalid color names file {file!r}. ' |
2930 | 2939 | f'Every line must be formatted as "name: color".') |
2931 | | - # Immediately add all open colors |
| 2940 | + |
| 2941 | + # Add all open colors |
2932 | 2942 | 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_) |
2935 | 2946 | 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 |
2943 | 2955 | break |
2944 | 2956 | for regex, sub in FILTER_TRANSLATIONS: |
2945 | 2957 | name = regex.sub(sub, name) |
2946 | 2958 | if name in seen or FILTER_BAD.search(name): |
2947 | 2959 | continue |
2948 | 2960 | 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 |
2955 | 2963 |
|
2956 | 2964 | # Remove colors that are 'too similar' by rounding to the nearest n units |
2957 | 2965 | # WARNING: Unique axis argument requires numpy version >=1.13 |
2958 | | - scale = (360, 100, 100) |
| 2966 | + hcls = np.array(hcls) |
2959 | 2967 | if hcls.size > 0: |
2960 | | - hcls = hcls / np.array(scale) |
| 2968 | + hcls = hcls / np.array([360, 100, 100]) |
2961 | 2969 | 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_) |
2976 | 2978 |
|
2977 | 2979 |
|
2978 | 2980 | @_timer |
@@ -3279,12 +3281,13 @@ def show_colors(nbreak=17, minsat=0.2): |
3279 | 3281 | for open_colors in (True, False): |
3280 | 3282 | scale = (360, 100, 100) |
3281 | 3283 | if open_colors: |
3282 | | - group = ['open'] |
| 3284 | + cats = ['open'] |
3283 | 3285 | 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] |
3288 | 3291 |
|
3289 | 3292 | # Group colors together by discrete range of hue, then sort by value |
3290 | 3293 | # For opencolors this is not necessary |
@@ -3314,7 +3317,7 @@ def show_colors(nbreak=17, minsat=0.2): |
3314 | 3317 | value, |
3315 | 3318 | FILTER_SPACE), |
3316 | 3319 | scale)] |
3317 | | - for key, value in icolors.items() |
| 3320 | + for key, value in data.items() |
3318 | 3321 | } |
3319 | 3322 | # Separate into columns and roughly sort by brightness in columns |
3320 | 3323 | # group in blocks of 20 hues |
@@ -3372,7 +3375,7 @@ def show_colors(nbreak=17, minsat=0.2): |
3372 | 3375 | xi_text = wsep * (col + 0.25 * swatch + 0.03 * swatch) |
3373 | 3376 | ax.text(xi_text, y, name, ha='left', va='center') |
3374 | 3377 | ax.hlines(y_line, xi_line, xf_line, |
3375 | | - color=icolors[name], lw=hsep * 0.6) |
| 3378 | + color=data[name], lw=hsep * 0.6) |
3376 | 3379 | # Apply formatting |
3377 | 3380 | ax.format(xlim=(0, X), ylim=(0, Y)) |
3378 | 3381 | ax.set_axis_off() |
@@ -3582,11 +3585,8 @@ def show_fonts(*args, size=12): |
3582 | 3585 | #: List of registered color cycle names. |
3583 | 3586 | cycles = [] # track *all* color cycles |
3584 | 3587 |
|
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 = {} |
3590 | 3590 |
|
3591 | 3591 | #: Registered font names. |
3592 | 3592 | fonts = [] |
|
0 commit comments