matplotlib-map-utils 1.0.2__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ from .north_arrow import _DEFAULTS_NA
2
+ from .scale_bar import _DEFAULTS_SB
3
+
4
+ __all__ = ["_DEFAULTS_NA","_DEFAULTS_SB"]
@@ -1,8 +1,8 @@
1
- ############################################################
2
- # defaults.py contains default values for the artists
1
+ #####################################################################
2
+ # defaults/north_arrow.py contains default values for the NorthArrows
3
3
  # at difference plot sizes (xsmall to xlarge)
4
4
  # see their corresponding sizes under each default heading
5
- ############################################################
5
+ #####################################################################
6
6
 
7
7
  # The main variables that update are the following:
8
8
  # base: scale, linewidth
@@ -407,7 +407,7 @@ _AOB_XL = {
407
407
 
408
408
  ## CONTAINER
409
409
  # This makes an easy-to-call dictionary of all the defaults we've set, for easy unpacking by the set_size function
410
- _DEFAULT_CONTAINER = {
410
+ _DEFAULTS_NA = {
411
411
  "xs":[_SCALE_XS, _BASE_XS, _FANCY_XS, _LABEL_XS, _SHADOW_XS, _PACK_XS, _AOB_XS],
412
412
  "sm":[_SCALE_SM, _BASE_SM, _FANCY_SM, _LABEL_SM, _SHADOW_SM, _PACK_SM, _AOB_SM],
413
413
  "md":[_SCALE_MD, _BASE_MD, _FANCY_MD, _LABEL_MD, _SHADOW_MD, _PACK_MD, _AOB_MD],
@@ -0,0 +1,377 @@
1
+ #################################################################
2
+ # defaults/scale_bar.py contains default values for the ScaleBars
3
+ # at difference plot sizes (xsmall to xlarge)
4
+ # see their corresponding sizes under each default heading
5
+ #################################################################
6
+
7
+ # The main variables that update are the following:
8
+ # bar: height, edgewidth, tickwidth
9
+ # labels: sep, style
10
+ # units: sep
11
+ # text: fontsize, stroke_width (also changes labels and units)
12
+ # aob: pad, borderpad
13
+
14
+ ## X-SMALL DEFAULTS
15
+ # Should work well for ~A8ish paper (2 to 3 inches, or 5 to 8 cm)
16
+ # The arrow will appear to be ~1/10 of an inch in height
17
+
18
+ # Bar
19
+ _BAR_XS = {
20
+ "projection":None,
21
+ "unit":None,
22
+ "rotation":0,
23
+ "max":None,
24
+ "length":None,
25
+ "height":0.05, # changed
26
+ "reverse":False,
27
+ "major_div":None,
28
+ "minor_div":None,
29
+ "minor_frac":0.66,
30
+ "minor_type":"none",
31
+ "facecolors":["black","white"],
32
+ "edgecolors":"black",
33
+ "edgewidth":0.5, # changed
34
+ "tick_loc":"above",
35
+ "basecolors":["black"],
36
+ "tickcolors":["black"],
37
+ "tickwidth":0.5 # changed
38
+ }
39
+
40
+ # Labels
41
+ _LABELS_XS = {
42
+ "labels":None,
43
+ "format":".2f",
44
+ "format_int":True,
45
+ "style":"first_last", # changed
46
+ "loc":"above",
47
+ "pad":0,
48
+ "sep":1.5, # changed
49
+ }
50
+
51
+ # Units
52
+ _UNITS_XS = {
53
+ "label":None,
54
+ "loc":"bar",
55
+ "pad":0,
56
+ "sep":1.5, # changed
57
+ }
58
+
59
+ # Text
60
+ _TEXT_XS = {
61
+ "fontsize":4, # changed
62
+ "textcolor":"black",
63
+ "fontfamily":"sans-serif",
64
+ "fontstyle":"normal",
65
+ "fontweight":"regular",
66
+ "stroke_width":0.5, # changed
67
+ "stroke_color":"white",
68
+ "rotation":None,
69
+ "rotation_mode":"anchor",
70
+ }
71
+
72
+ # AOB
73
+ _AOB_XS = {
74
+ "facecolor":None,
75
+ "edgecolor":None,
76
+ "alpha":None,
77
+ "pad":0.1, # changed
78
+ "borderpad":0.1, # changed
79
+ "prop":"medium",
80
+ "frameon":False,
81
+ "bbox_to_anchor":None,
82
+ "bbox_transform":None
83
+ }
84
+
85
+ ## SMALL DEFAULTS
86
+ # Should work well for ~A6 paper (4 to 6 inches, or 11 to 15 cm)
87
+ # The arrow will appear to be ~1/4 of an inch in height
88
+
89
+ # Bar
90
+ _BAR_SM = {
91
+ "projection":None,
92
+ "unit":None,
93
+ "rotation":0,
94
+ "max":None,
95
+ "length":None,
96
+ "height":0.075, # changed
97
+ "reverse":False,
98
+ "major_div":None,
99
+ "minor_div":None,
100
+ "minor_frac":0.66,
101
+ "minor_type":"none",
102
+ "facecolors":["black","white"],
103
+ "edgecolors":"black",
104
+ "edgewidth":0.75, # changed
105
+ "tick_loc":"above",
106
+ "basecolors":["black"],
107
+ "tickcolors":["black"],
108
+ "tickwidth":0.75 # changed
109
+ }
110
+
111
+ # Labels
112
+ _LABELS_SM = {
113
+ "labels":None,
114
+ "format":".2f",
115
+ "format_int":True,
116
+ "style":"first_last", # changed
117
+ "loc":"above",
118
+ "pad":0,
119
+ "sep":3, # changed
120
+ }
121
+
122
+ # Units
123
+ _UNITS_SM = {
124
+ "label":None,
125
+ "loc":"bar",
126
+ "pad":0,
127
+ "sep":3, # changed
128
+ }
129
+
130
+ # Text
131
+ _TEXT_SM = {
132
+ "fontsize":6, # changed
133
+ "textcolor":"black",
134
+ "fontfamily":"sans-serif",
135
+ "fontstyle":"normal",
136
+ "fontweight":"regular",
137
+ "stroke_width":0.5, # changed
138
+ "stroke_color":"white",
139
+ "rotation":None,
140
+ "rotation_mode":"anchor",
141
+ }
142
+
143
+ # AOB
144
+ _AOB_SM = {
145
+ "facecolor":None,
146
+ "edgecolor":None,
147
+ "alpha":None,
148
+ "pad":0.33, # changed
149
+ "borderpad":0.33, # changed
150
+ "prop":"medium",
151
+ "frameon":False,
152
+ "bbox_to_anchor":None,
153
+ "bbox_transform":None
154
+ }
155
+
156
+ ## MEDIUM DEFAULTS
157
+ # Should work well for ~A4/Letter paper (8 to 12 inches, or 21 to 30 cm)
158
+ # The arrow will appear to be ~ 1/2 an inch or ~1 cm in height
159
+
160
+ # Bar
161
+ _BAR_MD = {
162
+ "projection":None,
163
+ "unit":None,
164
+ "rotation":0,
165
+ "max":None,
166
+ "length":None,
167
+ "height":0.1, # changed
168
+ "reverse":False,
169
+ "major_div":None,
170
+ "minor_div":None,
171
+ "minor_frac":0.66,
172
+ "minor_type":"first",
173
+ "facecolors":["black","white"],
174
+ "edgecolors":"black",
175
+ "edgewidth":1, # changed
176
+ "tick_loc":"above",
177
+ "basecolors":["black"],
178
+ "tickcolors":["black"],
179
+ "tickwidth":1.5 # changed
180
+ }
181
+
182
+ # Labels
183
+ _LABELS_MD = {
184
+ "labels":None,
185
+ "format":".2f",
186
+ "format_int":True,
187
+ "style":"major",
188
+ "loc":"above",
189
+ "pad":0,
190
+ "sep":5, # changed
191
+ }
192
+
193
+ # Units
194
+ _UNITS_MD = {
195
+ "label":None,
196
+ "loc":"bar",
197
+ "pad":0,
198
+ "sep":5, # changed
199
+ }
200
+
201
+ # Text
202
+ _TEXT_MD = {
203
+ "fontsize":12, # changed
204
+ "textcolor":"black",
205
+ "fontfamily":"sans-serif",
206
+ "fontstyle":"normal",
207
+ "fontweight":"regular",
208
+ "stroke_width":1, # changed
209
+ "stroke_color":"white",
210
+ "rotation":None,
211
+ "rotation_mode":"anchor",
212
+ }
213
+
214
+ # AOB
215
+ _AOB_MD = {
216
+ "facecolor":None,
217
+ "edgecolor":None,
218
+ "alpha":None,
219
+ "pad":0.5, # changed
220
+ "borderpad":0.5, # changed
221
+ "prop":"medium",
222
+ "frameon":False,
223
+ "bbox_to_anchor":None,
224
+ "bbox_transform":None
225
+ }
226
+
227
+ ## LARGE DEFAULTS
228
+ # Should work well for ~A2 paper (16 to 24 inches, or 42 to 60 cm)
229
+ # The arrow will appear to be ~an inch in height
230
+
231
+ # Bar
232
+ _BAR_LG = {
233
+ "projection":None,
234
+ "unit":None,
235
+ "rotation":0,
236
+ "max":None,
237
+ "length":None,
238
+ "height":0.2, # changed
239
+ "reverse":False,
240
+ "major_div":None,
241
+ "minor_div":None,
242
+ "minor_frac":0.66,
243
+ "minor_type":"first",
244
+ "facecolors":["black","white"],
245
+ "edgecolors":"black",
246
+ "edgewidth":2, # changed
247
+ "tick_loc":"above",
248
+ "basecolors":["black"],
249
+ "tickcolors":["black"],
250
+ "tickwidth":3 # changed
251
+ }
252
+
253
+ # Labels
254
+ _LABELS_LG = {
255
+ "labels":None,
256
+ "format":".2f",
257
+ "format_int":True,
258
+ "style":"major",
259
+ "loc":"above",
260
+ "pad":0,
261
+ "sep":8, # changed
262
+ }
263
+
264
+ # Units
265
+ _UNITS_LG = {
266
+ "label":None,
267
+ "loc":"bar",
268
+ "pad":0,
269
+ "sep":8, # changed
270
+ }
271
+
272
+ # Text
273
+ _TEXT_LG = {
274
+ "fontsize":24, # changed
275
+ "textcolor":"black",
276
+ "fontfamily":"sans-serif",
277
+ "fontstyle":"normal",
278
+ "fontweight":"regular",
279
+ "stroke_width":2, # changed
280
+ "stroke_color":"white",
281
+ "rotation":None,
282
+ "rotation_mode":"anchor",
283
+ }
284
+
285
+ # AOB
286
+ _AOB_LG = {
287
+ "facecolor":None,
288
+ "edgecolor":None,
289
+ "alpha":None,
290
+ "pad":1, # changed
291
+ "borderpad":1, # changed
292
+ "prop":"medium",
293
+ "frameon":False,
294
+ "bbox_to_anchor":None,
295
+ "bbox_transform":None
296
+ }
297
+
298
+ ## X-LARGE DEFAULTS
299
+ # Should work well for ~A0/Poster paper (33 to 47 inches, or 85 to 120 cm)
300
+ # The arrow will appear to be ~2 inches in height
301
+
302
+ # Bar
303
+ _BAR_XL = {
304
+ "projection":None,
305
+ "unit":None,
306
+ "rotation":0,
307
+ "max":None,
308
+ "length":None,
309
+ "height":0.4, # changed
310
+ "reverse":False,
311
+ "major_div":None,
312
+ "minor_div":None,
313
+ "minor_frac":0.66,
314
+ "minor_type":"first",
315
+ "facecolors":["black","white"],
316
+ "edgecolors":"black",
317
+ "edgewidth":4, # changed
318
+ "tick_loc":"above",
319
+ "basecolors":["black"],
320
+ "tickcolors":["black"],
321
+ "tickwidth":5 # changed
322
+ }
323
+
324
+ # Labels
325
+ _LABELS_XL = {
326
+ "labels":None,
327
+ "format":".2f",
328
+ "format_int":True,
329
+ "style":"major",
330
+ "loc":"above",
331
+ "pad":0,
332
+ "sep":12, # changed
333
+ }
334
+
335
+ # Units
336
+ _UNITS_XL = {
337
+ "label":None,
338
+ "loc":"bar",
339
+ "pad":0,
340
+ "sep":12, # changed
341
+ }
342
+
343
+ # Text
344
+ _TEXT_XL = {
345
+ "fontsize":48, # changed
346
+ "textcolor":"black",
347
+ "fontfamily":"sans-serif",
348
+ "fontstyle":"normal",
349
+ "fontweight":"regular",
350
+ "stroke_width":4, # changed
351
+ "stroke_color":"white",
352
+ "rotation":None,
353
+ "rotation_mode":"anchor",
354
+ }
355
+
356
+ # AOB
357
+ _AOB_XL = {
358
+ "facecolor":None,
359
+ "edgecolor":None,
360
+ "alpha":None,
361
+ "pad":2, # changed
362
+ "borderpad":2, # changed
363
+ "prop":"medium",
364
+ "frameon":False,
365
+ "bbox_to_anchor":None,
366
+ "bbox_transform":None
367
+ }
368
+
369
+ ## CONTAINER
370
+ # This makes an easy-to-call dictionary of all the defaults we've set, for easy unpacking by the set_size function
371
+ _DEFAULTS_SB = {
372
+ "xs":[_BAR_XS, _LABELS_XS, _UNITS_XS, _TEXT_XS, _AOB_XS],
373
+ "sm":[_BAR_SM, _LABELS_SM, _UNITS_SM, _TEXT_SM, _AOB_SM],
374
+ "md":[_BAR_MD, _LABELS_MD, _UNITS_MD, _TEXT_MD, _AOB_MD],
375
+ "lg":[_BAR_LG, _LABELS_LG, _UNITS_LG, _TEXT_LG, _AOB_LG],
376
+ "xl":[_BAR_XL, _LABELS_XL, _UNITS_XL, _TEXT_XL, _AOB_XL],
377
+ }
@@ -0,0 +1,2 @@
1
+ from .north_arrow import *
2
+ from .scale_bar import *
@@ -0,0 +1,231 @@
1
+ # Default packages
2
+ import warnings
3
+ # Geo packages
4
+ import pyproj
5
+ # matplotlib's useful validation functions
6
+ import matplotlib.rcsetup
7
+
8
+ ### VALIDITY CHECKS ###
9
+ # Functions and variables used for validating inputs for classes
10
+ # All have a similar form, taking in the name of the property (prop), the value (val)
11
+ # some parameters to check against (min/max, list, type, etc.),
12
+ # and whether or not None is acceptable value
13
+
14
+ def _validate_list(prop, val, list, none_ok=False):
15
+ if none_ok==False and val is None:
16
+ raise ValueError(f"None is not a valid value for {prop}, please provide a value in this list: {list}")
17
+ elif none_ok==True and val is None:
18
+ return val
19
+ elif not val in list:
20
+ raise ValueError(f"'{val}' is not a valid value for {prop}, please provide a value in this list: {list}")
21
+ return val
22
+
23
+ def _validate_range(prop, val, min, max, none_ok=False):
24
+ if none_ok==False and val is None:
25
+ raise ValueError(f"None is not a valid value for {prop}, please provide a value between {min} and {max}")
26
+ elif none_ok==True and val is None:
27
+ return val
28
+ elif type(val) != int and type(val) != float:
29
+ raise ValueError(f"The supplied type is not valid for {prop}, please provide a float or integer between {min} and {max}")
30
+ elif max is not None:
31
+ if not val >= min and not val <= max:
32
+ raise ValueError(f"'{val}' is not a valid value for {prop}, please provide a value between {min} and {max}")
33
+ elif max is None:
34
+ if not val >= min:
35
+ raise ValueError(f"'{val}' is not a valid value for {prop}, please provide a value greater than {min}")
36
+ return val
37
+
38
+ def _validate_type(prop, val, match, none_ok=False):
39
+ if none_ok==False and val is None:
40
+ raise ValueError(f"None is not a valid value for {prop}, please provide an object of type {match}")
41
+ elif none_ok==True and val is None:
42
+ return val
43
+ elif not type(val)==match:
44
+ raise ValueError(f"'{val}' is not a valid value for {prop}, please provide an object of type {match}")
45
+ return val
46
+
47
+ def _validate_types(prop, val, matches, none_ok=False):
48
+ if none_ok==False and val is None:
49
+ raise ValueError(f"None is not a valid value for {prop}, please provide an object of type {matches}")
50
+ elif none_ok==True and val is None:
51
+ return val
52
+ elif not type(val) in matches:
53
+ raise ValueError(f"'{val}' is not a valid value for {prop}, please provide an object of type {matches}")
54
+ return val
55
+
56
+ def _validate_coords(prop, val, numpy_type, dims, none_ok=False):
57
+ if none_ok==False and val is None:
58
+ raise ValueError(f"None is not a valid value for {prop}, please provide an object of type {numpy_type}")
59
+ elif none_ok==True and val is None:
60
+ return val
61
+ elif not type(val)==numpy_type:
62
+ raise ValueError(f"'{val}' is not a valid value for {prop}, please provide an object of type {numpy_type}")
63
+ elif not val.ndim==dims:
64
+ raise ValueError(f"'{val}' is not a valid value for {prop}, please provide a numpy array with {dims} dimensions")
65
+ return val
66
+
67
+ def _validate_tuple(prop, val, length, types, none_ok=False):
68
+ if none_ok==False and val is None:
69
+ raise ValueError(f"None is not a valid value for {prop}, please provide a tuple of length {length} instead")
70
+ elif none_ok==True and val is None:
71
+ return val
72
+ elif type(val)!=tuple:
73
+ raise ValueError(f"{val} is not a valid value for {prop}, please provide a tuple of length {length} instead")
74
+ elif len(val)!=length:
75
+ raise ValueError(f"{val} is not a valid value for {prop}, please provide a tuple of length {length} instead")
76
+ else:
77
+ for item in val:
78
+ if type(item) not in types:
79
+ raise ValueError(f"{type(item)} is not a valid value for the items in {prop}, please provide a value of one of the following types: {types}")
80
+ return val
81
+
82
+ def _validate_color_or_none(prop, val, none_ok=False):
83
+ if none_ok==False and val is None:
84
+ raise ValueError(f"None is not a valid value for {prop}, please provide a color string acceptable to matplotlib instead")
85
+ elif none_ok==True and val is None:
86
+ return val
87
+ else:
88
+ matplotlib.rcsetup.validate_color(val)
89
+ return val
90
+
91
+ # NOTE: This one is a bit messy, particularly with the rotation module, but I can't think of a better way to do it...
92
+ def _validate_crs(prop, val, rotation_dict, none_ok=False):
93
+ degrees = rotation_dict.get("degrees",None)
94
+ crs = rotation_dict.get("crs",None)
95
+ reference = rotation_dict.get("reference",None)
96
+ coords = rotation_dict.get("coords",None)
97
+
98
+ if degrees is None:
99
+ if reference == "center":
100
+ if crs is None:
101
+ raise ValueError(f"If degrees is set to None, and reference is 'center', then a valid crs must be supplied")
102
+ else:
103
+ if crs is None or reference is None or coords is None:
104
+ raise ValueError(f"If degrees is set to None, then crs, reference, and coords cannot be None: please provide a valid input for each of these variables instead")
105
+ elif (type(degrees)==int or type(degrees)==float) and (crs is not None or reference is not None or coords is not None):
106
+ warnings.warn(f"A value for degrees was supplied; values for crs, reference, and coords will be ignored")
107
+ return val
108
+ else:
109
+ if none_ok==False and val is None:
110
+ raise ValueError(f"If degrees is set to None, then {prop} cannot be None: please provide a valid CRS input for PyProj instead")
111
+ elif none_ok==True and val is None:
112
+ return val
113
+ # This happens if (a) a value for CRS is supplied and (b) a value for degrees is NOT supplied
114
+ if type(val)==pyproj.CRS:
115
+ pass
116
+ else:
117
+ try:
118
+ val = pyproj.CRS.from_user_input(val)
119
+ except:
120
+ raise Exception(f"Invalid CRS supplied ({val}), please provide a valid CRS input for PyProj instead")
121
+ return val
122
+
123
+ # A simpler validation function for CRSs
124
+ def _validate_projection(prop, val, none_ok=False):
125
+ if type(val)==pyproj.CRS:
126
+ pass
127
+ else:
128
+ try:
129
+ val = pyproj.CRS.from_user_input(val)
130
+ except:
131
+ raise Exception(f"Invalid CRS supplied ({val}), please provide a valid CRS input for PyProj instead")
132
+ return val
133
+
134
+ # It is specifically to apply another validation function to the items in a list
135
+ # Ex. if we want to validate a LIST of colors instead of a single color
136
+ def _validate_iterable(prop, val, func, kwargs=None):
137
+ # Making sure we wrap everything in a list
138
+ if type(val) not in [tuple, list]:
139
+ val = [val]
140
+ # Then, we apply our validation func with optional kwargs to each item in said list, relying on it to return an error value
141
+ if kwargs is not None:
142
+ for v in val:
143
+ v = func(prop=prop, val=v, **kwargs)
144
+ return val
145
+ # The matplotlib built-in functions DON'T have that, and only ever take the one value
146
+ else:
147
+ for v in val:
148
+ v = func(v)
149
+ return val
150
+
151
+ # This is specifically to apply multiple validation functions to a value, if needed
152
+ # Ex. If an item can be a string OR a list of strings, we can use this to validate it
153
+ # "labels":{"func":_validate_multiple, "kwargs":{"funcs":[_validate_type, _validate_list], "kwargs":[{"match":str, "none_ok":True}, {"list":["major","first_last","minor_all","minor_first"], "none_ok":True}]}}, # any string or any item in the list
154
+ def _validate_multiple(prop, val, funcs, kwargs):
155
+ # Simply iterate through each func and kwarg
156
+ for f,k in zip(funcs,kwargs):
157
+ # We wrap the attempts in a try block to suppress the errors
158
+ try:
159
+ v = f(prop=prop, val=v, **k)
160
+ # If we pass, we can stop here and return the value
161
+ return val
162
+ except:
163
+ continue
164
+ # If we didn't return a value and exit the loop yet, then the passed value is incorrect, as we raise an error
165
+ raise ValueError(f"{val} is not a valid value for {prop}, please provide check the documentation")
166
+
167
+ # This final one is used for keys that are not validated
168
+ def _skip_validation(val, none_ok=False):
169
+ return val
170
+
171
+
172
+ ### MORE VALIDITY FUNCTIONS ###
173
+ # These are more customized, and so are separated from the _validate_* functions above
174
+ # Mainly, they can process the input dictionaries wholesale, as well as the individual functions in it
175
+ def _validate_dict(input_dict, default_dict, functions, to_validate=None, return_clean=False, parse_false=True):
176
+ if input_dict == False:
177
+ if parse_false == True:
178
+ return None
179
+ else:
180
+ return False
181
+ elif input_dict is None or input_dict == True:
182
+ return default_dict
183
+ elif type(input_dict) != dict:
184
+ raise ValueError(f"A dictionary (NoneType) must be provided, please double-check your inputs")
185
+ else:
186
+ values = default_dict | input_dict
187
+ # Pre-checking that no invalid keys are passed
188
+ invalid = [key for key in values.keys() if key not in functions.keys() and key not in ["bbox_to_anchor", "bbox_transform"]]
189
+ if len(invalid) > 0:
190
+ print(f"Warning: Invalid keys detected ({invalid}). These will be ignored.")
191
+ # First, trimming our values to only those we need to validate
192
+ if to_validate == "input":
193
+ values = {key: val for key, val in values.items() if (key in input_dict.keys() and key in functions.keys())} # have to check against both here
194
+ functions = {key: val for key, val in functions.items() if key in values.keys()}
195
+ elif to_validate is not None:
196
+ values = {key: val for key, val in values.items() if key in to_validate}
197
+ functions = {key: val for key, val in functions.items() if key in values.keys()}
198
+ else:
199
+ values = {key: val for key, val in values.items() if key in functions.keys()}
200
+ functions = {key: val for key, val in functions.items() if key in values.keys()}
201
+ # Now, running the function with the necessary kwargs
202
+ for key,val in values.items():
203
+ fd = functions[key]
204
+ func = fd["func"]
205
+ # NOTE: This is messy but the only way to get the rotation value to the crs function
206
+ if key=="crs":
207
+ _ = func(prop=key, val=val, rotation_dict=values, **fd["kwargs"])
208
+ # NOTE: This is messy but the only way to check the projection dict without keywords
209
+ elif key=="projection":
210
+ _ = func(prop=key, val=val)
211
+ # Our custom functions always have this dictionary key in them, so we know what form they take
212
+ elif "kwargs" in fd:
213
+ _ = func(prop=key, val=val, **fd["kwargs"])
214
+ # The matplotlib built-in functions DON'T have that, and only ever take the one value
215
+ else:
216
+ _ = func(val)
217
+ if return_clean==True:
218
+ return values
219
+
220
+ # This function can process the _VALIDATE dictionaries we established above, but for single variables at a time
221
+ def _validate(validate_dict, prop, val, return_val=True, kwargs={}):
222
+ fd = validate_dict[prop]
223
+ func = fd["func"]
224
+ # Our custom functions always have this dictionary key in them, so we know what form they take
225
+ if "kwargs" in fd:
226
+ val = func(prop=prop, val=val, **(fd["kwargs"] | kwargs))
227
+ # The matplotlib built-in functions DON'T have that, and only ever take the one value
228
+ else:
229
+ val = func(val)
230
+ if return_val==True:
231
+ return val