ncplot 0.3.13__tar.gz → 0.4.0__tar.gz
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.
- {ncplot-0.3.13 → ncplot-0.4.0}/PKG-INFO +1 -1
- {ncplot-0.3.13 → ncplot-0.4.0}/ncplot/plot.py +182 -4
- {ncplot-0.3.13 → ncplot-0.4.0}/setup.py +1 -1
- {ncplot-0.3.13 → ncplot-0.4.0}/LICENSE +0 -0
- {ncplot-0.3.13 → ncplot-0.4.0}/MANIFEST.in +0 -0
- {ncplot-0.3.13 → ncplot-0.4.0}/README.md +0 -0
- {ncplot-0.3.13 → ncplot-0.4.0}/ncplot/__init__.py +0 -0
- {ncplot-0.3.13 → ncplot-0.4.0}/ncplot/command_line.py +0 -0
- {ncplot-0.3.13 → ncplot-0.4.0}/ncplot/deprecated.py +0 -0
- {ncplot-0.3.13 → ncplot-0.4.0}/ncplot/utils.py +0 -0
- {ncplot-0.3.13 → ncplot-0.4.0}/ncplot/xarray.py +0 -0
- {ncplot-0.3.13 → ncplot-0.4.0}/ncplot.egg-info/SOURCES.txt +0 -0
- {ncplot-0.3.13 → ncplot-0.4.0}/pyproject.toml +0 -0
- {ncplot-0.3.13 → ncplot-0.4.0}/requirements.txt +0 -0
- {ncplot-0.3.13 → ncplot-0.4.0}/setup.cfg +0 -0
|
@@ -2,6 +2,7 @@ import sys
|
|
|
2
2
|
import warnings
|
|
3
3
|
from threading import Thread
|
|
4
4
|
|
|
5
|
+
import glob
|
|
5
6
|
import time
|
|
6
7
|
import holoviews as hv
|
|
7
8
|
import panel as pn
|
|
@@ -165,6 +166,172 @@ def in_notebook(out=None):
|
|
|
165
166
|
return "ipykernel" in sys.modules
|
|
166
167
|
|
|
167
168
|
|
|
169
|
+
def _looks_like_time_coord(coord, coord_name):
|
|
170
|
+
"""
|
|
171
|
+
Check whether a coordinate is likely to represent time.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
if coord_name is None:
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
if coord_name == "time" or coord_name.startswith("time"):
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
if "time" in coord_name:
|
|
181
|
+
return True
|
|
182
|
+
|
|
183
|
+
if coord.attrs.get("standard_name") == "time":
|
|
184
|
+
return True
|
|
185
|
+
|
|
186
|
+
if coord.attrs.get("axis") == "T":
|
|
187
|
+
return True
|
|
188
|
+
|
|
189
|
+
if np.issubdtype(coord.dtype, np.datetime64):
|
|
190
|
+
return True
|
|
191
|
+
|
|
192
|
+
values = coord.values
|
|
193
|
+
if len(values) > 0:
|
|
194
|
+
first_value = values.ravel()[0]
|
|
195
|
+
if hasattr(first_value, "year") and hasattr(first_value, "month"):
|
|
196
|
+
return True
|
|
197
|
+
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _find_time_coord(ds):
|
|
202
|
+
"""
|
|
203
|
+
Identify the most likely time coordinate name for a dataset.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
df_dims = get_dims(ds)
|
|
207
|
+
time_name = df_dims.time[0]
|
|
208
|
+
|
|
209
|
+
if time_name is not None:
|
|
210
|
+
return time_name
|
|
211
|
+
|
|
212
|
+
time_candidates = []
|
|
213
|
+
for coord_name in list(ds.coords):
|
|
214
|
+
coord = ds[coord_name]
|
|
215
|
+
if _looks_like_time_coord(coord, coord_name):
|
|
216
|
+
time_candidates.append(coord_name)
|
|
217
|
+
|
|
218
|
+
if len(time_candidates) == 1:
|
|
219
|
+
return time_candidates[0]
|
|
220
|
+
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _combine_datasets(datasets):
|
|
225
|
+
"""
|
|
226
|
+
Combine multiple datasets by time or by variables.
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
if len(datasets) == 1:
|
|
230
|
+
return datasets[0]
|
|
231
|
+
|
|
232
|
+
normalized = []
|
|
233
|
+
for ds in datasets:
|
|
234
|
+
time_name = _find_time_coord(ds)
|
|
235
|
+
if time_name is not None and time_name != "time":
|
|
236
|
+
ds = ds.rename({time_name: "time"})
|
|
237
|
+
normalized.append(ds)
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
return xr.combine_by_coords(normalized)
|
|
241
|
+
except Exception:
|
|
242
|
+
try:
|
|
243
|
+
return xr.merge(normalized, compat="override")
|
|
244
|
+
except Exception:
|
|
245
|
+
return xr.concat(normalized, dim="time")
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _normalize_time_coord(ds):
|
|
249
|
+
"""
|
|
250
|
+
Normalize detected time coordinate names to ``time``.
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
time_name = _find_time_coord(ds)
|
|
254
|
+
if time_name is not None and time_name != "time":
|
|
255
|
+
return ds.rename({time_name: "time"})
|
|
256
|
+
|
|
257
|
+
return ds
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _open_multiple_files(files):
|
|
261
|
+
"""
|
|
262
|
+
Open multiple files into one dataset.
|
|
263
|
+
|
|
264
|
+
This uses ``open_mfdataset`` first, then falls back to manual per-file
|
|
265
|
+
opening and combining if xarray cannot infer a valid combine strategy.
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
return xr.open_mfdataset(
|
|
270
|
+
files,
|
|
271
|
+
combine="by_coords",
|
|
272
|
+
preprocess=_normalize_time_coord,
|
|
273
|
+
)
|
|
274
|
+
except Exception:
|
|
275
|
+
datasets = [xr.open_dataset(ff) for ff in files]
|
|
276
|
+
return _combine_datasets(datasets)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _load_input_as_dataset(x):
|
|
280
|
+
"""
|
|
281
|
+
Load a path, path glob, or sequence of paths into a single dataset.
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
if isinstance(x, (list, tuple, set)):
|
|
285
|
+
files = [os.fspath(ff) for ff in x]
|
|
286
|
+
return _open_multiple_files(files)
|
|
287
|
+
|
|
288
|
+
if isinstance(x, str) and glob.has_magic(x):
|
|
289
|
+
files = sorted(glob.glob(x))
|
|
290
|
+
if len(files) == 0:
|
|
291
|
+
raise FileNotFoundError(f"No files matched pattern {x}")
|
|
292
|
+
return _open_multiple_files(files)
|
|
293
|
+
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _get_color_limits(ds, vars, kwargs):
|
|
298
|
+
"""
|
|
299
|
+
Work out the value range to use for a plot.
|
|
300
|
+
|
|
301
|
+
For time-varying plots, this is evaluated across the full dataset so the
|
|
302
|
+
color scale stays consistent between time steps.
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
if "clim" in kwargs:
|
|
306
|
+
clim_min, clim_max = kwargs["clim"]
|
|
307
|
+
if clim_min < 0 < clim_max:
|
|
308
|
+
v_max = float(max(-clim_min, clim_max))
|
|
309
|
+
return (-v_max, v_max)
|
|
310
|
+
|
|
311
|
+
return (float(clim_min), float(clim_max))
|
|
312
|
+
|
|
313
|
+
data = ds[vars]
|
|
314
|
+
data_min = float(data.min(skipna=True).values)
|
|
315
|
+
data_max = float(data.max(skipna=True).values)
|
|
316
|
+
|
|
317
|
+
if data_min < 0 < data_max:
|
|
318
|
+
v_max = float(max(-data_min, data_max))
|
|
319
|
+
return (-v_max, v_max)
|
|
320
|
+
|
|
321
|
+
return (data_min, data_max)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _apply_color_limits(plot, ds, vars, autoscale, kwargs):
|
|
325
|
+
"""
|
|
326
|
+
Apply a stable color range to a plot when autoscaling is enabled.
|
|
327
|
+
"""
|
|
328
|
+
|
|
329
|
+
if autoscale:
|
|
330
|
+
return plot.redim.range(**{vars: _get_color_limits(ds, vars, kwargs)})
|
|
331
|
+
|
|
332
|
+
return plot
|
|
333
|
+
|
|
334
|
+
|
|
168
335
|
def view(x, vars=None, autoscale=True, out=None, **kwargs):
|
|
169
336
|
"""
|
|
170
337
|
Plot the contents of a NetCDF out
|
|
@@ -209,6 +376,10 @@ def view(x, vars=None, autoscale=True, out=None, **kwargs):
|
|
|
209
376
|
if type(x) is xr.core.dataarray.DataArray:
|
|
210
377
|
x = x.to_dataset()
|
|
211
378
|
|
|
379
|
+
loaded_ds = _load_input_as_dataset(x)
|
|
380
|
+
if loaded_ds is not None:
|
|
381
|
+
x = loaded_ds
|
|
382
|
+
|
|
212
383
|
if type(x) is xr.core.dataset.Dataset:
|
|
213
384
|
xr_file = True
|
|
214
385
|
else:
|
|
@@ -963,7 +1134,8 @@ def view(x, vars=None, autoscale=True, out=None, **kwargs):
|
|
|
963
1134
|
#responsive=(in_notebook() is False),
|
|
964
1135
|
responsive = False,
|
|
965
1136
|
**kwargs,
|
|
966
|
-
)
|
|
1137
|
+
)
|
|
1138
|
+
intplot = _apply_color_limits(intplot, ds, vars, autoscale, kwargs)
|
|
967
1139
|
else:
|
|
968
1140
|
intplot = ds.hvplot.quadmesh(
|
|
969
1141
|
lon_name,
|
|
@@ -978,6 +1150,7 @@ def view(x, vars=None, autoscale=True, out=None, **kwargs):
|
|
|
978
1150
|
responsive = False,
|
|
979
1151
|
**kwargs,
|
|
980
1152
|
)
|
|
1153
|
+
intplot = _apply_color_limits(intplot, ds, vars, autoscale, kwargs)
|
|
981
1154
|
else:
|
|
982
1155
|
if coastline:
|
|
983
1156
|
coastline = get_coastline(ds, lon_name, lat_name)
|
|
@@ -1002,7 +1175,8 @@ def view(x, vars=None, autoscale=True, out=None, **kwargs):
|
|
|
1002
1175
|
#responsive=(in_notebook() is False),
|
|
1003
1176
|
responsive = False,
|
|
1004
1177
|
**kwargs,
|
|
1005
|
-
)
|
|
1178
|
+
)
|
|
1179
|
+
intplot = _apply_color_limits(intplot, ds, vars, autoscale, kwargs)
|
|
1006
1180
|
else:
|
|
1007
1181
|
intplot = ds.hvplot.image(
|
|
1008
1182
|
lon_name,
|
|
@@ -1054,7 +1228,8 @@ def view(x, vars=None, autoscale=True, out=None, **kwargs):
|
|
|
1054
1228
|
#responsive=(in_notebook() is False),
|
|
1055
1229
|
responsive = False,
|
|
1056
1230
|
**kwargs,
|
|
1057
|
-
)
|
|
1231
|
+
)
|
|
1232
|
+
intplot = _apply_color_limits(intplot, ds, vars, autoscale, kwargs)
|
|
1058
1233
|
else:
|
|
1059
1234
|
intplot = ds.hvplot.quadmesh(
|
|
1060
1235
|
lon_name,
|
|
@@ -1069,6 +1244,7 @@ def view(x, vars=None, autoscale=True, out=None, **kwargs):
|
|
|
1069
1244
|
responsive = False,
|
|
1070
1245
|
**kwargs,
|
|
1071
1246
|
)
|
|
1247
|
+
intplot = _apply_color_limits(intplot, ds, vars, autoscale, kwargs)
|
|
1072
1248
|
|
|
1073
1249
|
else:
|
|
1074
1250
|
|
|
@@ -1095,7 +1271,8 @@ def view(x, vars=None, autoscale=True, out=None, **kwargs):
|
|
|
1095
1271
|
#responsive=(in_notebook() is False),
|
|
1096
1272
|
responsive = False,
|
|
1097
1273
|
**kwargs,
|
|
1098
|
-
)
|
|
1274
|
+
)
|
|
1275
|
+
intplot = _apply_color_limits(intplot, ds, vars, autoscale, kwargs)
|
|
1099
1276
|
else:
|
|
1100
1277
|
intplot = ds.hvplot.image(
|
|
1101
1278
|
lon_name,
|
|
@@ -1110,6 +1287,7 @@ def view(x, vars=None, autoscale=True, out=None, **kwargs):
|
|
|
1110
1287
|
responsive = False,
|
|
1111
1288
|
**kwargs,
|
|
1112
1289
|
)
|
|
1290
|
+
intplot = _apply_color_limits(intplot, ds, vars, autoscale, kwargs)
|
|
1113
1291
|
|
|
1114
1292
|
if in_notebook(out):
|
|
1115
1293
|
if out is None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|