@@ -894,54 +894,6 @@ def xyy(ix, funcs=funcs):
894894 # Return copy
895895 return self .updated (name = name , segmentdata = segmentdata , ** kwargs )
896896
897- @staticmethod
898- def from_list (name , colors , ratios = None , ** kwargs ):
899- """
900- Make a `LinearSegmentedColormap` from a list of colors.
901-
902- Parameters
903- ----------
904- name : str
905- The colormap name.
906- colors : list of color-spec or (float, color-spec) tuples, optional
907- If list of RGB[A] tuples or color strings, the colormap transitions
908- evenly from ``colors[0]`` at the left-hand side to
909- ``colors[-1]`` at the right-hand side.
910-
911- If list of (float, color-spec) tuples, the float values are the
912- coordinate of each transition and must range from 0 to 1. This
913- can be used to divide the colormap range unevenly.
914- ratios : list of float, optional
915- Relative extents of each color transition. Must have length
916- ``len(colors) - 1``. Larger numbers indicate a slower
917- transition, smaller numbers indicate a faster transition.
918-
919- Other parameters
920- ----------------
921- **kwargs
922- Passed to `LinearSegmentedColormap`.
923-
924- Returns
925- -------
926- `LinearSegmentedColormap`
927- The colormap.
928- """
929- # Get coordinates
930- coords = None
931- if not np .iterable (colors ):
932- raise ValueError (f'Colors must be iterable, got colors={ colors !r} ' )
933- if (np .iterable (colors [0 ]) and len (colors [0 ]) == 2
934- and not isinstance (colors [0 ], str )):
935- coords , colors = zip (* colors )
936- colors = [to_rgb (color , alpha = True ) for color in colors ]
937-
938- # Build segmentdata
939- keys = ('red' , 'green' , 'blue' , 'alpha' )
940- cdict = {}
941- for key , values in zip (keys , zip (* colors )):
942- cdict [key ] = _make_segmentdata_array (values , coords , ratios )
943- return LinearSegmentedColormap (name , cdict , ** kwargs )
944-
945897 def punched (self , cut = None , name = None , ** kwargs ):
946898 """
947899 Return a version of the colormap with the center "punched out".
@@ -1238,6 +1190,77 @@ def updated(
12381190 cmap ._rgba_over = self ._rgba_over
12391191 return cmap
12401192
1193+ @staticmethod
1194+ def from_file (path ):
1195+ """
1196+ Load colormap from a file.
1197+ Valid file extensions are described in the below table.
1198+
1199+ ===================== =============================================================================================================================================================================================================
1200+ Extension Description
1201+ ===================== =============================================================================================================================================================================================================
1202+ ``.hex`` List of HEX strings in any format (comma-separated, separate lines, with double quotes... anything goes).'ColorBlind10':
1203+ ``.xml`` XML files with ``<Point .../>`` entries specifying ``x``, ``r``, ``g``, ``b``, and optionally, ``a`` values, where ``x`` is the colormap coordinate and the rest are the RGB and opacity (or "alpha") values.
1204+ ``.rgb`` 3-column table delimited by commas or consecutive spaces, each column indicating red, blue and green color values.
1205+ ``.xrgb`` As with ``.rgb``, but with 4 columns. The first column indicates the colormap coordinate.
1206+ ``.rgba``, ``.xrgba`` As with ``.rgb``, ``.xrgb``, but with a trailing opacity (or "alpha") column.
1207+ ===================== =============================================================================================================================================================================================================
1208+
1209+ Parameters
1210+ ----------
1211+ path : str
1212+ The file path.
1213+ """ # noqa
1214+ return _from_file (path , listed = False )
1215+
1216+ @staticmethod
1217+ def from_list (name , colors , ratios = None , ** kwargs ):
1218+ """
1219+ Make a `LinearSegmentedColormap` from a list of colors.
1220+
1221+ Parameters
1222+ ----------
1223+ name : str
1224+ The colormap name.
1225+ colors : list of color-spec or (float, color-spec) tuples, optional
1226+ If list of RGB[A] tuples or color strings, the colormap transitions
1227+ evenly from ``colors[0]`` at the left-hand side to
1228+ ``colors[-1]`` at the right-hand side.
1229+
1230+ If list of (float, color-spec) tuples, the float values are the
1231+ coordinate of each transition and must range from 0 to 1. This
1232+ can be used to divide the colormap range unevenly.
1233+ ratios : list of float, optional
1234+ Relative extents of each color transition. Must have length
1235+ ``len(colors) - 1``. Larger numbers indicate a slower
1236+ transition, smaller numbers indicate a faster transition.
1237+
1238+ Other parameters
1239+ ----------------
1240+ **kwargs
1241+ Passed to `LinearSegmentedColormap`.
1242+
1243+ Returns
1244+ -------
1245+ `LinearSegmentedColormap`
1246+ The colormap.
1247+ """
1248+ # Get coordinates
1249+ coords = None
1250+ if not np .iterable (colors ):
1251+ raise ValueError (f'Colors must be iterable, got colors={ colors !r} ' )
1252+ if (np .iterable (colors [0 ]) and len (colors [0 ]) == 2
1253+ and not isinstance (colors [0 ], str )):
1254+ coords , colors = zip (* colors )
1255+ colors = [to_rgb (color , alpha = True ) for color in colors ]
1256+
1257+ # Build segmentdata
1258+ keys = ('red' , 'green' , 'blue' , 'alpha' )
1259+ cdict = {}
1260+ for key , values in zip (keys , zip (* colors )):
1261+ cdict [key ] = _make_segmentdata_array (values , coords , ratios )
1262+ return LinearSegmentedColormap (name , cdict , ** kwargs )
1263+
12411264
12421265class ListedColormap (mcolors .ListedColormap , _Colormap ):
12431266 r"""
@@ -1410,6 +1433,29 @@ def updated(self, colors=None, name=None, N=None, *, alpha=None):
14101433 cmap ._rgba_over = self ._rgba_over
14111434 return cmap
14121435
1436+ @staticmethod
1437+ def from_file (path ):
1438+ """
1439+ Load color cycle from a file.
1440+ Valid file extensions are described in the below table.
1441+
1442+ ===================== =============================================================================================================================================================================================================
1443+ Extension Description
1444+ ===================== =============================================================================================================================================================================================================
1445+ ``.hex`` List of HEX strings in any format (comma-separated, separate lines, with double quotes... anything goes).'ColorBlind10':
1446+ ``.xml`` XML files with ``<Point .../>`` entries specifying ``x``, ``r``, ``g``, ``b``, and optionally, ``a`` values, where ``x`` is the colormap coordinate and the rest are the RGB and opacity (or "alpha") values.
1447+ ``.rgb`` 3-column table delimited by commas or consecutive spaces, each column indicating red, blue and green color values.
1448+ ``.xrgb`` As with ``.rgb``, but with 4 columns. The first column indicates the colormap coordinate.
1449+ ``.rgba``, ``.xrgba`` As with ``.rgb``, ``.xrgb``, but with a trailing opacity (or "alpha") column.
1450+ ===================== =============================================================================================================================================================================================================
1451+
1452+ Parameters
1453+ ----------
1454+ path : str
1455+ The file path.
1456+ """ # noqa
1457+ return _from_file (path , listed = True )
1458+
14131459
14141460class PerceptuallyUniformColormap (LinearSegmentedColormap , _Colormap ):
14151461 """Similar to `~matplotlib.colors.LinearSegmentedColormap`, but instead
@@ -2098,12 +2144,16 @@ def Colormap(
20982144 # TODO: Document how 'listmode' also affects loaded files
20992145 if isinstance (cmap , str ):
21002146 if '.' in cmap :
2101- if os .path .isfile (os .path .expanduser (cmap )):
2102- tmp , cmap = _load_cmap_cycle (
2103- cmap , cmap = (listmode != 'listed' ))
2104- else :
2147+ isfile = os .path .isfile (os .path .expanduser (cmap ))
2148+ if isfile :
2149+ if listmode == 'listed' :
2150+ cmap = ListedColormap .from_file (cmap )
2151+ else :
2152+ cmap = LinearSegmentedColormap .from_file (cmap )
2153+ if not isfile or not cmap :
21052154 raise FileNotFoundError (
2106- f'Colormap or cycle file { cmap !r} not found.'
2155+ f'Colormap or cycle file { cmap !r} not found '
2156+ 'or failed to load.'
21072157 )
21082158 else :
21092159 try :
@@ -2742,31 +2792,30 @@ def _get_data_paths(dirname):
27422792 ]
27432793
27442794
2745- def _load_cmap_cycle (filename , cmap = False ):
2746- """
2747- Helper function that reads generalized colormap and color cycle files.
2748- """
2749- N = rcParams ['image.lut' ] # query this when register function is called
2795+ def _from_file (filename , listed = False ):
2796+ """Read generalized colormap and color cycle files."""
27502797 filename = os .path .expanduser (filename )
27512798 if os .path .isdir (filename ): # no warning
2752- return None , None
2799+ return
27532800
27542801 # Directly read segmentdata json file
27552802 # NOTE: This is special case! Immediately return name and cmap
2803+ N = rcParams ['image.lut' ]
27562804 name , ext = os .path .splitext (os .path .basename (filename ))
27572805 ext = ext [1 :]
2806+ cmap = None
27582807 if ext == 'json' :
27592808 with open (filename , 'r' ) as f :
27602809 data = json .load (f )
27612810 kw = {}
27622811 for key in ('cyclic' , 'gamma' , 'gamma1' , 'gamma2' , 'space' ):
27632812 kw [key ] = data .pop (key , None )
27642813 if 'red' in data :
2765- data = LinearSegmentedColormap (name , data , N = N )
2814+ cmap = LinearSegmentedColormap (name , data , N = N )
27662815 else :
2767- data = PerceptuallyUniformColormap (name , data , N = N , ** kw )
2816+ cmap = PerceptuallyUniformColormap (name , data , N = N , ** kw )
27682817 if name [- 2 :] == '_r' :
2769- data = data .reversed (name [:- 2 ])
2818+ cmap = cmap .reversed (name [:- 2 ])
27702819
27712820 # Read .rgb, .rgba, .xrgb, and .xrgba files
27722821 elif ext in ('txt' , 'rgb' , 'xrgb' , 'rgba' , 'xrgba' ):
@@ -2781,14 +2830,16 @@ def _load_cmap_cycle(filename, cmap=False):
27812830 except ValueError :
27822831 _warn_proplot (
27832832 f'Failed to load { filename !r} . Expected a table of comma '
2784- 'or space-separated values.' )
2833+ 'or space-separated values.'
2834+ )
27852835 return None , None
27862836 # Build x-coordinates and standardize shape
27872837 data = np .array (data )
27882838 if data .shape [1 ] != len (ext ):
27892839 _warn_proplot (
27902840 f'Failed to load { filename !r} . Got { data .shape [1 ]} columns, '
2791- f'but expected { len (ext )} .' )
2841+ f'but expected { len (ext )} .'
2842+ )
27922843 return None , None
27932844 if ext [0 ] != 'x' : # i.e. no x-coordinates specified explicitly
27942845 x = np .linspace (0 , 1 , data .shape [0 ])
@@ -2803,20 +2854,16 @@ def _load_cmap_cycle(filename, cmap=False):
28032854 doc = ElementTree .parse (filename )
28042855 except IOError :
28052856 _warn_proplot (f'Failed to load { filename !r} .' )
2806- return None , None
2857+ return
28072858 x , data = [], []
28082859 for s in doc .getroot ().findall ('.//Point' ):
28092860 # Verify keys
28102861 if any (key not in s .attrib for key in 'xrgb' ):
28112862 _warn_proplot (
28122863 f'Failed to load { filename !r} . Missing an x, r, g, or b '
2813- 'specification inside one or more <Point> tags.' )
2814- return None , None
2815- if 'o' in s .attrib and 'a' in s .attrib :
2816- _warn_proplot (
2817- f'Failed to load { filename !r} . Contains '
2818- 'ambiguous opacity key.' )
2819- return None , None
2864+ 'specification inside one or more <Point> tags.'
2865+ )
2866+ return
28202867 # Get data
28212868 color = []
28222869 for key in 'rgbao' : # o for opacity
@@ -2826,11 +2873,13 @@ def _load_cmap_cycle(filename, cmap=False):
28262873 x .append (float (s .attrib ['x' ]))
28272874 data .append (color )
28282875 # Convert to array
2829- if not all (len (data [0 ]) == len (color ) for color in data ):
2876+ if not all (len (data [0 ]) == len (color )
2877+ and len (color ) in (3 , 4 ) for color in data ):
28302878 _warn_proplot (
2831- f'File { filename !r} has some points with alpha channel '
2832- 'specified, some without.' )
2833- return None , None
2879+ f'Failed to load { filename !r} . Unexpected number of channels '
2880+ 'or mixed channels across <Point> tags.'
2881+ )
2882+ return
28342883
28352884 # Read hex strings
28362885 elif ext == 'hex' :
@@ -2839,24 +2888,22 @@ def _load_cmap_cycle(filename, cmap=False):
28392888 data = re .findall ('#[0-9a-fA-F]{6}' , string ) # list of strings
28402889 if len (data ) < 2 :
28412890 _warn_proplot (
2842- f'Failed to load { filename !r} . Hex strings not found.' )
2843- return None , None
2891+ f'Failed to load { filename !r} . Hex strings not found.'
2892+ )
2893+ return
28442894 # Convert to array
28452895 x = np .linspace (0 , 1 , len (data ))
28462896 data = [to_rgb (color ) for color in data ]
28472897 else :
28482898 _warn_proplot (
2849- f'Colormap or cycle file { filename !r} has unknown extension.' )
2850- return None , None
2899+ f'Colormap or cycle file { filename !r} has unknown extension.'
2900+ )
2901+ return
28512902
28522903 # Standardize and reverse if necessary to cmap
28532904 # TODO: Document the fact that filenames ending in _r return a reversed
28542905 # version of the colormap stored in that file.
2855- if isinstance (data , LinearSegmentedColormap ):
2856- if not cmap :
2857- _warn_proplot (f'Failed to load { filename !r} as color cycle.' )
2858- return None , None
2859- else :
2906+ if not cmap :
28602907 x , data = np .array (x ), np .array (data )
28612908 # for some reason, some aren't in 0-1 range
28622909 x = (x - x .min ()) / (x .max () - x .min ())
@@ -2866,12 +2913,14 @@ def _load_cmap_cycle(filename, cmap=False):
28662913 name = name [:- 2 ]
28672914 data = data [::- 1 , :]
28682915 x = 1 - x [::- 1 ]
2869- if cmap :
2916+ if listed :
2917+ cmap = ListedColormap (data , name , N = len (data ))
2918+ else :
28702919 data = [(x , color ) for x , color in zip (x , data )]
2871- data = LinearSegmentedColormap .from_list (name , data , N = N )
2920+ cmap = LinearSegmentedColormap .from_list (name , data , N = N )
28722921
28732922 # Return colormap or data
2874- return name , data
2923+ return cmap
28752924
28762925
28772926@_timer
0 commit comments