jarvisplot 1.3.5__tar.gz → 1.3.7__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.
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/JarvisPLOT.egg-info/PKG-INFO +1 -1
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/JarvisPLOT.egg-info/SOURCES.txt +5 -2
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/PKG-INFO +1 -1
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/Figure/adapters_rect.py +471 -1
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/Figure/adapters_ternary.py +21 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/Figure/colorbar_runtime.py +64 -15
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/Figure/config_runtime.py +8 -7
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/Figure/figure.py +18 -58
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/Figure/helper.py +1 -1
- jarvisplot-1.3.7/jarvisplot/Figure/interp_natural_neighbor.py +507 -0
- jarvisplot-1.3.7/jarvisplot/Figure/interp_natural_neighbor_exact.py +1068 -0
- jarvisplot-1.3.7/jarvisplot/Figure/layer_runtime.py +642 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/Figure/layout_runtime.py +1 -1
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/Figure/method_registry.py +17 -79
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/Figure/preprocessor.py +11 -42
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/Figure/preprocessor_runtime.py +170 -7
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/Figure/profile_runtime.py +1 -1
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/core.py +23 -48
- jarvisplot-1.3.7/jarvisplot/core_assets.py +68 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/core_runtime.py +0 -59
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/data_loader.py +10 -110
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/data_loader_runtime.py +47 -61
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/inner_func.py +2 -2
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/utils/cmaps.py +1 -14
- jarvisplot-1.3.7/jarvisplot/utils/dataframes.py +56 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/pyproject.toml +1 -1
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/tests/test_data_loader_contracts.py +122 -207
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/tests/test_docs_status_labels.py +5 -1
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/tests/test_figure_contracts.py +281 -15
- jarvisplot-1.3.7/tests/test_interp_natural_neighbor.py +391 -0
- jarvisplot-1.3.7/tests/test_interpolator_contracts.py +44 -0
- jarvisplot-1.3.7/tests/test_jpcontour_methods.py +746 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/tests/test_parse_data_metadata.py +0 -1
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/tests/test_pathing_contracts.py +8 -4
- jarvisplot-1.3.7/tests/test_template_json.py +26 -0
- jarvisplot-1.3.5/jarvisplot/Figure/adapters.py +0 -6
- jarvisplot-1.3.5/jarvisplot/Figure/layer_runtime.py +0 -309
- jarvisplot-1.3.5/jarvisplot/Figure/load_data.py +0 -169
- jarvisplot-1.3.5/jarvisplot/core_assets.py +0 -63
- jarvisplot-1.3.5/tests/test_interpolator_contracts.py +0 -9
- jarvisplot-1.3.5/tests/test_template_json.py +0 -10
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/JarvisPLOT.egg-info/dependency_links.txt +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/JarvisPLOT.egg-info/entry_points.txt +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/JarvisPLOT.egg-info/requires.txt +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/JarvisPLOT.egg-info/top_level.txt +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/README.md +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/Figure/__init__.py +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/Figure/data_pipelines.py +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/Figure/style_runtime.py +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/__init__.py +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cache_store.py +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/2x1/rect.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/2x1/rect5x1.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/2x1/ternary.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/4x1/rect.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/4x1/ternary.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/args.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/colors/colormaps.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/default/output.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/gambit/2x1/rect_cmap.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/gambit/2x1/ternary.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/icons/gambit.png +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/icons/gambit_small.png +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cards/style_preference.json +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/cli.py +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/client.py +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/config.py +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/data_loader_hdf5.py +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/data_loader_summary.py +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/memtrace.py +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/utils/__init__.py +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/utils/expression.py +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/utils/interpolator.py +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/jarvisplot/utils/pathing.py +0 -0
- {jarvisplot-1.3.5 → jarvisplot-1.3.7}/setup.cfg +0 -0
|
@@ -21,7 +21,6 @@ jarvisplot/data_loader_summary.py
|
|
|
21
21
|
jarvisplot/inner_func.py
|
|
22
22
|
jarvisplot/memtrace.py
|
|
23
23
|
jarvisplot/Figure/__init__.py
|
|
24
|
-
jarvisplot/Figure/adapters.py
|
|
25
24
|
jarvisplot/Figure/adapters_rect.py
|
|
26
25
|
jarvisplot/Figure/adapters_ternary.py
|
|
27
26
|
jarvisplot/Figure/colorbar_runtime.py
|
|
@@ -29,9 +28,10 @@ jarvisplot/Figure/config_runtime.py
|
|
|
29
28
|
jarvisplot/Figure/data_pipelines.py
|
|
30
29
|
jarvisplot/Figure/figure.py
|
|
31
30
|
jarvisplot/Figure/helper.py
|
|
31
|
+
jarvisplot/Figure/interp_natural_neighbor.py
|
|
32
|
+
jarvisplot/Figure/interp_natural_neighbor_exact.py
|
|
32
33
|
jarvisplot/Figure/layer_runtime.py
|
|
33
34
|
jarvisplot/Figure/layout_runtime.py
|
|
34
|
-
jarvisplot/Figure/load_data.py
|
|
35
35
|
jarvisplot/Figure/method_registry.py
|
|
36
36
|
jarvisplot/Figure/preprocessor.py
|
|
37
37
|
jarvisplot/Figure/preprocessor_runtime.py
|
|
@@ -61,13 +61,16 @@ jarvisplot/cards/icons/gambit.png
|
|
|
61
61
|
jarvisplot/cards/icons/gambit_small.png
|
|
62
62
|
jarvisplot/utils/__init__.py
|
|
63
63
|
jarvisplot/utils/cmaps.py
|
|
64
|
+
jarvisplot/utils/dataframes.py
|
|
64
65
|
jarvisplot/utils/expression.py
|
|
65
66
|
jarvisplot/utils/interpolator.py
|
|
66
67
|
jarvisplot/utils/pathing.py
|
|
67
68
|
tests/test_data_loader_contracts.py
|
|
68
69
|
tests/test_docs_status_labels.py
|
|
69
70
|
tests/test_figure_contracts.py
|
|
71
|
+
tests/test_interp_natural_neighbor.py
|
|
70
72
|
tests/test_interpolator_contracts.py
|
|
73
|
+
tests/test_jpcontour_methods.py
|
|
71
74
|
tests/test_parse_data_metadata.py
|
|
72
75
|
tests/test_pathing_contracts.py
|
|
73
76
|
tests/test_template_json.py
|
|
@@ -5,12 +5,16 @@ from typing import Any, Dict, Optional
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import os
|
|
7
7
|
import json
|
|
8
|
+
import warnings
|
|
8
9
|
|
|
9
10
|
from matplotlib.axes import Axes
|
|
10
11
|
from matplotlib.collections import PolyCollection
|
|
11
12
|
#
|
|
12
13
|
from .helper import _auto_clip, _mask_by_extend, voronoi_finite_polygons_2d, _clip_poly_to_rect
|
|
13
14
|
from .profile_runtime import grid_profile_mesh
|
|
15
|
+
from .interp_natural_neighbor import resolve_backend
|
|
16
|
+
|
|
17
|
+
DEFAULT_JP_SAMPLE_GRID = 500
|
|
14
18
|
|
|
15
19
|
|
|
16
20
|
# —— Basic Adapter: Forward to the underlying Axes, merge default parameters, perform automatic clipping ——
|
|
@@ -52,6 +56,314 @@ class StdAxesAdapter:
|
|
|
52
56
|
base.update(kwargs or {})
|
|
53
57
|
return base
|
|
54
58
|
|
|
59
|
+
@staticmethod
|
|
60
|
+
def _coerce_positive_int(value, default: int) -> int:
|
|
61
|
+
try:
|
|
62
|
+
n = int(value)
|
|
63
|
+
if n > 0:
|
|
64
|
+
return n
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
return int(default)
|
|
68
|
+
|
|
69
|
+
def _resolve_jpcontour_axis(self, axis_name: str, values, *, npts: int, bounds=None):
|
|
70
|
+
values = np.asarray(values, dtype=float).reshape(-1)
|
|
71
|
+
finite = values[np.isfinite(values)]
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
scale = str(self.ax.get_xscale() if axis_name == "x" else self.ax.get_yscale()).lower()
|
|
75
|
+
except Exception:
|
|
76
|
+
scale = "linear"
|
|
77
|
+
|
|
78
|
+
if bounds is None:
|
|
79
|
+
if finite.size:
|
|
80
|
+
lo = float(np.min(finite))
|
|
81
|
+
hi = float(np.max(finite))
|
|
82
|
+
else:
|
|
83
|
+
try:
|
|
84
|
+
lo, hi = self.ax.get_xlim() if axis_name == "x" else self.ax.get_ylim()
|
|
85
|
+
lo = float(lo)
|
|
86
|
+
hi = float(hi)
|
|
87
|
+
except Exception:
|
|
88
|
+
lo, hi = 0.0, 1.0
|
|
89
|
+
else:
|
|
90
|
+
try:
|
|
91
|
+
lo = float(bounds[0])
|
|
92
|
+
hi = float(bounds[1])
|
|
93
|
+
except Exception:
|
|
94
|
+
lo, hi = 0.0, 1.0
|
|
95
|
+
|
|
96
|
+
if not np.isfinite(lo) or not np.isfinite(hi):
|
|
97
|
+
if finite.size:
|
|
98
|
+
lo = float(np.min(finite))
|
|
99
|
+
hi = float(np.max(finite))
|
|
100
|
+
else:
|
|
101
|
+
lo, hi = 0.0, 1.0
|
|
102
|
+
|
|
103
|
+
if lo == hi:
|
|
104
|
+
if scale == "log" and lo > 0:
|
|
105
|
+
hi = lo * 10.0
|
|
106
|
+
else:
|
|
107
|
+
hi = lo + 1.0
|
|
108
|
+
|
|
109
|
+
if scale == "log":
|
|
110
|
+
positive = finite[finite > 0]
|
|
111
|
+
if lo <= 0 or hi <= 0:
|
|
112
|
+
if positive.size:
|
|
113
|
+
lo = float(np.min(positive))
|
|
114
|
+
hi = float(np.max(positive))
|
|
115
|
+
else:
|
|
116
|
+
warnings.warn(
|
|
117
|
+
f"jpcontour: {axis_name}-axis is log-scaled but has no positive finite values; "
|
|
118
|
+
"falling back to a linear grid",
|
|
119
|
+
RuntimeWarning,
|
|
120
|
+
stacklevel=3,
|
|
121
|
+
)
|
|
122
|
+
return np.linspace(lo, hi, npts)
|
|
123
|
+
lo = max(lo, np.finfo(float).tiny)
|
|
124
|
+
hi = max(hi, lo * 10.0)
|
|
125
|
+
return np.geomspace(lo, hi, npts)
|
|
126
|
+
|
|
127
|
+
return np.linspace(lo, hi, npts)
|
|
128
|
+
|
|
129
|
+
def _normalize_jpfield_axis(self, axis_name: str, values, *, bounds=None):
|
|
130
|
+
"""Map data coordinates onto axes-fraction coordinates in [0, 1].
|
|
131
|
+
|
|
132
|
+
This is used by jpfield so interpolation and rendering both happen in
|
|
133
|
+
the axes transform frame, avoiding mixed log/linear geometry.
|
|
134
|
+
"""
|
|
135
|
+
values = np.asarray(values, dtype=float).reshape(-1)
|
|
136
|
+
out = np.full(values.shape, np.nan, dtype=float)
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
scale = str(self.ax.get_xscale() if axis_name == "x" else self.ax.get_yscale()).lower()
|
|
140
|
+
except Exception:
|
|
141
|
+
scale = "linear"
|
|
142
|
+
|
|
143
|
+
if bounds is None:
|
|
144
|
+
try:
|
|
145
|
+
bounds = self.ax.get_xlim() if axis_name == "x" else self.ax.get_ylim()
|
|
146
|
+
except Exception:
|
|
147
|
+
bounds = None
|
|
148
|
+
|
|
149
|
+
if bounds is None:
|
|
150
|
+
finite = values[np.isfinite(values)]
|
|
151
|
+
if finite.size:
|
|
152
|
+
lo = float(np.min(finite))
|
|
153
|
+
hi = float(np.max(finite))
|
|
154
|
+
else:
|
|
155
|
+
lo, hi = 0.0, 1.0
|
|
156
|
+
else:
|
|
157
|
+
try:
|
|
158
|
+
lo = float(bounds[0])
|
|
159
|
+
hi = float(bounds[1])
|
|
160
|
+
except Exception:
|
|
161
|
+
lo, hi = 0.0, 1.0
|
|
162
|
+
|
|
163
|
+
if not np.isfinite(lo) or not np.isfinite(hi):
|
|
164
|
+
finite = values[np.isfinite(values)]
|
|
165
|
+
if finite.size:
|
|
166
|
+
lo = float(np.min(finite))
|
|
167
|
+
hi = float(np.max(finite))
|
|
168
|
+
else:
|
|
169
|
+
lo, hi = 0.0, 1.0
|
|
170
|
+
|
|
171
|
+
if lo == hi:
|
|
172
|
+
if scale == "log" and lo > 0:
|
|
173
|
+
hi = lo * 10.0
|
|
174
|
+
else:
|
|
175
|
+
hi = lo + 1.0
|
|
176
|
+
|
|
177
|
+
if scale == "log":
|
|
178
|
+
positive = values[np.isfinite(values) & (values > 0)]
|
|
179
|
+
if lo <= 0 or hi <= 0:
|
|
180
|
+
if positive.size:
|
|
181
|
+
lo = float(np.min(positive))
|
|
182
|
+
hi = float(np.max(positive))
|
|
183
|
+
else:
|
|
184
|
+
warnings.warn(
|
|
185
|
+
f"jpfield: {axis_name}-axis is log-scaled but has no positive finite values; "
|
|
186
|
+
"rendering an empty field in axes-fraction space",
|
|
187
|
+
RuntimeWarning,
|
|
188
|
+
stacklevel=3,
|
|
189
|
+
)
|
|
190
|
+
return out
|
|
191
|
+
lo = max(lo, np.finfo(float).tiny)
|
|
192
|
+
hi = max(hi, lo * 10.0)
|
|
193
|
+
den = np.log(hi) - np.log(lo)
|
|
194
|
+
if den == 0:
|
|
195
|
+
den = 1.0
|
|
196
|
+
valid = np.isfinite(values) & (values > 0)
|
|
197
|
+
out[valid] = (np.log(values[valid]) - np.log(lo)) / den
|
|
198
|
+
return out
|
|
199
|
+
|
|
200
|
+
den = hi - lo
|
|
201
|
+
if den == 0:
|
|
202
|
+
den = 1.0
|
|
203
|
+
valid = np.isfinite(values)
|
|
204
|
+
out[valid] = (values[valid] - lo) / den
|
|
205
|
+
return out
|
|
206
|
+
|
|
207
|
+
def _build_jpfield_grid(
|
|
208
|
+
self,
|
|
209
|
+
x,
|
|
210
|
+
y,
|
|
211
|
+
*,
|
|
212
|
+
bin: int = DEFAULT_JP_SAMPLE_GRID,
|
|
213
|
+
nx: Optional[int] = None,
|
|
214
|
+
ny: Optional[int] = None,
|
|
215
|
+
xlim=None,
|
|
216
|
+
ylim=None,
|
|
217
|
+
):
|
|
218
|
+
x = np.asarray(x, dtype=float).reshape(-1)
|
|
219
|
+
y = np.asarray(y, dtype=float).reshape(-1)
|
|
220
|
+
if x.shape[0] != y.shape[0]:
|
|
221
|
+
raise ValueError("x and y must have the same length")
|
|
222
|
+
|
|
223
|
+
nx_eff = self._coerce_positive_int(nx if nx is not None else bin, DEFAULT_JP_SAMPLE_GRID)
|
|
224
|
+
ny_eff = self._coerce_positive_int(ny if ny is not None else bin, DEFAULT_JP_SAMPLE_GRID)
|
|
225
|
+
x_norm = self._normalize_jpfield_axis("x", x, bounds=xlim)
|
|
226
|
+
y_norm = self._normalize_jpfield_axis("y", y, bounds=ylim)
|
|
227
|
+
xq = np.linspace(0.0, 1.0, nx_eff)
|
|
228
|
+
yq = np.linspace(0.0, 1.0, ny_eff)
|
|
229
|
+
X, Y = np.meshgrid(xq, yq)
|
|
230
|
+
return X, Y, x_norm, y_norm
|
|
231
|
+
|
|
232
|
+
def _interpolate_jpfield_grid(
|
|
233
|
+
self,
|
|
234
|
+
x,
|
|
235
|
+
y,
|
|
236
|
+
z,
|
|
237
|
+
*,
|
|
238
|
+
interp_method: str = "natural_neighbor",
|
|
239
|
+
bin: int = DEFAULT_JP_SAMPLE_GRID,
|
|
240
|
+
nx: Optional[int] = None,
|
|
241
|
+
ny: Optional[int] = None,
|
|
242
|
+
xlim=None,
|
|
243
|
+
ylim=None,
|
|
244
|
+
nan_policy: str = "strict",
|
|
245
|
+
backend_options: Optional[dict[str, Any]] = None,
|
|
246
|
+
):
|
|
247
|
+
x = np.asarray(x, dtype=float).reshape(-1)
|
|
248
|
+
y = np.asarray(y, dtype=float).reshape(-1)
|
|
249
|
+
z = np.asarray(z, dtype=float).reshape(-1)
|
|
250
|
+
if x.shape[0] != y.shape[0] or x.shape[0] != z.shape[0]:
|
|
251
|
+
raise ValueError("x, y, and z must have the same length")
|
|
252
|
+
|
|
253
|
+
X, Y, x_norm, y_norm = self._build_jpfield_grid(
|
|
254
|
+
x,
|
|
255
|
+
y,
|
|
256
|
+
bin=bin,
|
|
257
|
+
nx=nx,
|
|
258
|
+
ny=ny,
|
|
259
|
+
xlim=xlim,
|
|
260
|
+
ylim=ylim,
|
|
261
|
+
)
|
|
262
|
+
try:
|
|
263
|
+
backend = resolve_backend(interp_method)
|
|
264
|
+
except Exception as exc:
|
|
265
|
+
raise ValueError(
|
|
266
|
+
f"Unsupported jpfield interpolation backend: {interp_method!r}"
|
|
267
|
+
) from exc
|
|
268
|
+
Z = backend(
|
|
269
|
+
x_norm,
|
|
270
|
+
y_norm,
|
|
271
|
+
z,
|
|
272
|
+
X,
|
|
273
|
+
Y,
|
|
274
|
+
nan_policy=nan_policy,
|
|
275
|
+
backend_options=backend_options,
|
|
276
|
+
)
|
|
277
|
+
return X, Y, np.asarray(Z, dtype=float)
|
|
278
|
+
|
|
279
|
+
def _build_jpcontour_grid(
|
|
280
|
+
self,
|
|
281
|
+
x,
|
|
282
|
+
y,
|
|
283
|
+
*,
|
|
284
|
+
bin: int = DEFAULT_JP_SAMPLE_GRID,
|
|
285
|
+
nx: Optional[int] = None,
|
|
286
|
+
ny: Optional[int] = None,
|
|
287
|
+
xlim=None,
|
|
288
|
+
ylim=None,
|
|
289
|
+
):
|
|
290
|
+
x = np.asarray(x, dtype=float).reshape(-1)
|
|
291
|
+
y = np.asarray(y, dtype=float).reshape(-1)
|
|
292
|
+
if x.shape[0] != y.shape[0]:
|
|
293
|
+
raise ValueError("x and y must have the same length")
|
|
294
|
+
|
|
295
|
+
if xlim is None:
|
|
296
|
+
try:
|
|
297
|
+
xlim = self.ax.get_xlim()
|
|
298
|
+
except Exception:
|
|
299
|
+
xlim = None
|
|
300
|
+
if ylim is None:
|
|
301
|
+
try:
|
|
302
|
+
ylim = self.ax.get_ylim()
|
|
303
|
+
except Exception:
|
|
304
|
+
ylim = None
|
|
305
|
+
|
|
306
|
+
nx_eff = self._coerce_positive_int(nx if nx is not None else bin, DEFAULT_JP_SAMPLE_GRID)
|
|
307
|
+
ny_eff = self._coerce_positive_int(ny if ny is not None else bin, DEFAULT_JP_SAMPLE_GRID)
|
|
308
|
+
xq = self._resolve_jpcontour_axis("x", x, npts=nx_eff, bounds=xlim)
|
|
309
|
+
yq = self._resolve_jpcontour_axis("y", y, npts=ny_eff, bounds=ylim)
|
|
310
|
+
return np.meshgrid(xq, yq)
|
|
311
|
+
|
|
312
|
+
def _interpolate_jpcontour_grid(
|
|
313
|
+
self,
|
|
314
|
+
x,
|
|
315
|
+
y,
|
|
316
|
+
z,
|
|
317
|
+
*,
|
|
318
|
+
interp_method: str = "natural_neighbor",
|
|
319
|
+
bin: int = DEFAULT_JP_SAMPLE_GRID,
|
|
320
|
+
nx: Optional[int] = None,
|
|
321
|
+
ny: Optional[int] = None,
|
|
322
|
+
xlim=None,
|
|
323
|
+
ylim=None,
|
|
324
|
+
nan_policy: str = "strict",
|
|
325
|
+
diagnostics: bool = False,
|
|
326
|
+
backend_options: Optional[dict[str, Any]] = None,
|
|
327
|
+
):
|
|
328
|
+
"""Interpolate scattered data on an axes-fraction grid for contour rendering.
|
|
329
|
+
|
|
330
|
+
This keeps jpcontour/jpcontourf aligned with jpfield: coordinates are
|
|
331
|
+
normalized into the current Axes frame before interpolation, then the
|
|
332
|
+
contour artist is drawn with transform=ax.transAxes.
|
|
333
|
+
"""
|
|
334
|
+
x = np.asarray(x, dtype=float).reshape(-1)
|
|
335
|
+
y = np.asarray(y, dtype=float).reshape(-1)
|
|
336
|
+
z = np.asarray(z, dtype=float).reshape(-1)
|
|
337
|
+
if x.shape[0] != y.shape[0] or x.shape[0] != z.shape[0]:
|
|
338
|
+
raise ValueError("x, y, and z must have the same length")
|
|
339
|
+
|
|
340
|
+
X, Y, x_norm, y_norm = self._build_jpfield_grid(
|
|
341
|
+
x,
|
|
342
|
+
y,
|
|
343
|
+
bin=bin,
|
|
344
|
+
nx=nx,
|
|
345
|
+
ny=ny,
|
|
346
|
+
xlim=xlim,
|
|
347
|
+
ylim=ylim,
|
|
348
|
+
)
|
|
349
|
+
try:
|
|
350
|
+
backend = resolve_backend(interp_method)
|
|
351
|
+
except Exception as exc:
|
|
352
|
+
raise ValueError(
|
|
353
|
+
f"Unsupported jpcontour interpolation backend: {interp_method!r}"
|
|
354
|
+
) from exc
|
|
355
|
+
Z = backend(
|
|
356
|
+
x_norm,
|
|
357
|
+
y_norm,
|
|
358
|
+
z,
|
|
359
|
+
X,
|
|
360
|
+
Y,
|
|
361
|
+
nan_policy=nan_policy,
|
|
362
|
+
diagnostics=diagnostics,
|
|
363
|
+
backend_options=backend_options,
|
|
364
|
+
)
|
|
365
|
+
return X, Y, np.asarray(Z, dtype=float)
|
|
366
|
+
|
|
55
367
|
# —— Common method forwarding (add as needed) ——
|
|
56
368
|
def scatter(self, **kwargs):
|
|
57
369
|
x, y = kwargs.pop("x"), kwargs.pop("y")
|
|
@@ -71,7 +383,7 @@ class StdAxesAdapter:
|
|
|
71
383
|
df = kwargs.pop("__df__", None)
|
|
72
384
|
kw = self._merge("grid_profile", kwargs)
|
|
73
385
|
|
|
74
|
-
#
|
|
386
|
+
# Strip kwargs that are not used by grid_profile rendering.
|
|
75
387
|
for k in ("shading", "levels", "extend", "space", "marker", "s", "c"):
|
|
76
388
|
kw.pop(k, None)
|
|
77
389
|
|
|
@@ -178,6 +490,164 @@ class StdAxesAdapter:
|
|
|
178
490
|
artists = self.ax.contourf(*args, **kw)
|
|
179
491
|
return _auto_clip(artists, self.ax, self._clip_path)
|
|
180
492
|
|
|
493
|
+
def jpcontour(
|
|
494
|
+
self,
|
|
495
|
+
x,
|
|
496
|
+
y,
|
|
497
|
+
z,
|
|
498
|
+
*args,
|
|
499
|
+
interp_method: str = "natural_neighbor",
|
|
500
|
+
bin: int = DEFAULT_JP_SAMPLE_GRID,
|
|
501
|
+
nx: Optional[int] = None,
|
|
502
|
+
ny: Optional[int] = None,
|
|
503
|
+
xlim=None,
|
|
504
|
+
ylim=None,
|
|
505
|
+
nan_policy: str = "strict",
|
|
506
|
+
diagnostics: bool = False,
|
|
507
|
+
backend_options: Optional[dict[str, Any]] = None,
|
|
508
|
+
**kwargs,
|
|
509
|
+
):
|
|
510
|
+
"""Scattered-data contour plot rendered in axes-fraction coordinates."""
|
|
511
|
+
kw = self._merge("contour", kwargs)
|
|
512
|
+
kw.pop("transform", None)
|
|
513
|
+
X, Y, Z = self._interpolate_jpcontour_grid(
|
|
514
|
+
x,
|
|
515
|
+
y,
|
|
516
|
+
z,
|
|
517
|
+
interp_method=interp_method,
|
|
518
|
+
bin=bin,
|
|
519
|
+
nx=nx,
|
|
520
|
+
ny=ny,
|
|
521
|
+
xlim=xlim,
|
|
522
|
+
ylim=ylim,
|
|
523
|
+
nan_policy=nan_policy,
|
|
524
|
+
diagnostics=diagnostics,
|
|
525
|
+
backend_options=backend_options,
|
|
526
|
+
)
|
|
527
|
+
artists = self.ax.contour(
|
|
528
|
+
X,
|
|
529
|
+
Y,
|
|
530
|
+
np.ma.masked_invalid(Z),
|
|
531
|
+
*args,
|
|
532
|
+
transform=self.ax.transAxes,
|
|
533
|
+
**kw,
|
|
534
|
+
)
|
|
535
|
+
return _auto_clip(artists, self.ax, self._clip_path)
|
|
536
|
+
|
|
537
|
+
def jpcontourf(
|
|
538
|
+
self,
|
|
539
|
+
x,
|
|
540
|
+
y,
|
|
541
|
+
z,
|
|
542
|
+
*args,
|
|
543
|
+
interp_method: str = "natural_neighbor",
|
|
544
|
+
bin: int = DEFAULT_JP_SAMPLE_GRID,
|
|
545
|
+
nx: Optional[int] = None,
|
|
546
|
+
ny: Optional[int] = None,
|
|
547
|
+
xlim=None,
|
|
548
|
+
ylim=None,
|
|
549
|
+
nan_policy: str = "strict",
|
|
550
|
+
diagnostics: bool = False,
|
|
551
|
+
backend_options: Optional[dict[str, Any]] = None,
|
|
552
|
+
**kwargs,
|
|
553
|
+
):
|
|
554
|
+
"""Filled scattered-data contour plot rendered in axes-fraction coordinates."""
|
|
555
|
+
kw = self._merge("contourf", kwargs)
|
|
556
|
+
kw.pop("transform", None)
|
|
557
|
+
X, Y, Z = self._interpolate_jpcontour_grid(
|
|
558
|
+
x,
|
|
559
|
+
y,
|
|
560
|
+
z,
|
|
561
|
+
interp_method=interp_method,
|
|
562
|
+
bin=bin,
|
|
563
|
+
nx=nx,
|
|
564
|
+
ny=ny,
|
|
565
|
+
xlim=xlim,
|
|
566
|
+
ylim=ylim,
|
|
567
|
+
nan_policy=nan_policy,
|
|
568
|
+
diagnostics=diagnostics,
|
|
569
|
+
backend_options=backend_options,
|
|
570
|
+
)
|
|
571
|
+
artists = self.ax.contourf(
|
|
572
|
+
X,
|
|
573
|
+
Y,
|
|
574
|
+
np.ma.masked_invalid(Z),
|
|
575
|
+
*args,
|
|
576
|
+
transform=self.ax.transAxes,
|
|
577
|
+
**kw,
|
|
578
|
+
)
|
|
579
|
+
return _auto_clip(artists, self.ax, self._clip_path)
|
|
580
|
+
|
|
581
|
+
def jpfield(
|
|
582
|
+
self,
|
|
583
|
+
x,
|
|
584
|
+
y,
|
|
585
|
+
z,
|
|
586
|
+
*args,
|
|
587
|
+
interp_method: str = "natural_neighbor",
|
|
588
|
+
bin: int = DEFAULT_JP_SAMPLE_GRID,
|
|
589
|
+
nx: Optional[int] = None,
|
|
590
|
+
ny: Optional[int] = None,
|
|
591
|
+
xlim=None,
|
|
592
|
+
ylim=None,
|
|
593
|
+
nan_policy: str = "strict",
|
|
594
|
+
backend_options: Optional[dict[str, Any]] = None,
|
|
595
|
+
shading: str = "auto",
|
|
596
|
+
**kwargs,
|
|
597
|
+
):
|
|
598
|
+
"""Scattered-data field plot.
|
|
599
|
+
|
|
600
|
+
Interpolates scattered x/y/z samples onto a regular grid in axes-fraction
|
|
601
|
+
space, then renders the result with pcolormesh(transform=ax.transAxes).
|
|
602
|
+
The plot is drawn from explicit cell edges so raster backends do not
|
|
603
|
+
introduce a half-cell seam.
|
|
604
|
+
Undefined regions remain masked. Contour-only kwargs such as levels /
|
|
605
|
+
colors / extend are ignored.
|
|
606
|
+
"""
|
|
607
|
+
kw = self._merge("pcolormesh", kwargs)
|
|
608
|
+
for k in ("levels", "extend", "locator", "corner_mask", "colors", "linestyles"):
|
|
609
|
+
kw.pop(k, None)
|
|
610
|
+
kw.pop("shading", None)
|
|
611
|
+
kw.pop("transform", None)
|
|
612
|
+
X, Y, Z = self._interpolate_jpfield_grid(
|
|
613
|
+
x,
|
|
614
|
+
y,
|
|
615
|
+
z,
|
|
616
|
+
interp_method=interp_method,
|
|
617
|
+
bin=bin,
|
|
618
|
+
nx=nx,
|
|
619
|
+
ny=ny,
|
|
620
|
+
xlim=xlim,
|
|
621
|
+
ylim=ylim,
|
|
622
|
+
nan_policy=nan_policy,
|
|
623
|
+
backend_options=backend_options,
|
|
624
|
+
)
|
|
625
|
+
X = np.asarray(X, dtype=float)
|
|
626
|
+
Y = np.asarray(Y, dtype=float)
|
|
627
|
+
Z = np.ma.masked_invalid(np.asarray(Z, dtype=float))
|
|
628
|
+
if str(shading).lower() in {"auto", "flat"}:
|
|
629
|
+
ny, nx = Z.shape
|
|
630
|
+
xe = np.linspace(0.0, 1.0, nx + 1)
|
|
631
|
+
ye = np.linspace(0.0, 1.0, ny + 1)
|
|
632
|
+
X, Y = np.meshgrid(xe, ye)
|
|
633
|
+
shading = "flat"
|
|
634
|
+
else:
|
|
635
|
+
shading = str(shading)
|
|
636
|
+
kw.setdefault("edgecolors", "none")
|
|
637
|
+
kw.setdefault("linewidth", 0.0)
|
|
638
|
+
kw.setdefault("antialiased", False)
|
|
639
|
+
kw.setdefault("snap", True)
|
|
640
|
+
artists = self.ax.pcolormesh(
|
|
641
|
+
X,
|
|
642
|
+
Y,
|
|
643
|
+
Z,
|
|
644
|
+
*args,
|
|
645
|
+
transform=self.ax.transAxes,
|
|
646
|
+
shading=shading,
|
|
647
|
+
**kw,
|
|
648
|
+
)
|
|
649
|
+
return _auto_clip(artists, self.ax, self._clip_path)
|
|
650
|
+
|
|
181
651
|
def imshow(self, *args, **kwargs):
|
|
182
652
|
kw = self._merge("imshow", kwargs)
|
|
183
653
|
artists = self.ax.imshow(*args, **kw)
|
|
@@ -87,3 +87,24 @@ class TernaryAxesAdapter(StdAxesAdapter):
|
|
|
87
87
|
return super().tricontourf(**kwargs)
|
|
88
88
|
# else:
|
|
89
89
|
# raise ValueError("scatter() needs either (a,b,c) or (x,y) inputs")
|
|
90
|
+
|
|
91
|
+
def jpcontour(self, *args, **kwargs):
|
|
92
|
+
if {"left", "right", "bottom"}.issubset(kwargs.keys()):
|
|
93
|
+
x, y = self._lbr_to_xy(kwargs.pop("left"), kwargs.pop("right"), kwargs.pop("bottom"))
|
|
94
|
+
kwargs["x"] = x
|
|
95
|
+
kwargs["y"] = y
|
|
96
|
+
return super().jpcontour(*args, **kwargs)
|
|
97
|
+
|
|
98
|
+
def jpcontourf(self, *args, **kwargs):
|
|
99
|
+
if {"left", "right", "bottom"}.issubset(kwargs.keys()):
|
|
100
|
+
x, y = self._lbr_to_xy(kwargs.pop("left"), kwargs.pop("right"), kwargs.pop("bottom"))
|
|
101
|
+
kwargs["x"] = x
|
|
102
|
+
kwargs["y"] = y
|
|
103
|
+
return super().jpcontourf(*args, **kwargs)
|
|
104
|
+
|
|
105
|
+
def jpfield(self, *args, **kwargs):
|
|
106
|
+
if {"left", "right", "bottom"}.issubset(kwargs.keys()):
|
|
107
|
+
x, y = self._lbr_to_xy(kwargs.pop("left"), kwargs.pop("right"), kwargs.pop("bottom"))
|
|
108
|
+
kwargs["x"] = x
|
|
109
|
+
kwargs["y"] = y
|
|
110
|
+
return super().jpfield(*args, **kwargs)
|