jarvisplot 1.3.6__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.
Files changed (78) hide show
  1. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/JarvisPLOT.egg-info/PKG-INFO +1 -1
  2. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/PKG-INFO +1 -1
  3. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/adapters_rect.py +42 -16
  4. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/interp_natural_neighbor_exact.py +4 -0
  5. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/layer_runtime.py +1 -1
  6. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/pyproject.toml +1 -1
  7. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/tests/test_interp_natural_neighbor.py +2 -1
  8. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/tests/test_jpcontour_methods.py +124 -7
  9. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/JarvisPLOT.egg-info/SOURCES.txt +0 -0
  10. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/JarvisPLOT.egg-info/dependency_links.txt +0 -0
  11. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/JarvisPLOT.egg-info/entry_points.txt +0 -0
  12. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/JarvisPLOT.egg-info/requires.txt +0 -0
  13. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/JarvisPLOT.egg-info/top_level.txt +0 -0
  14. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/README.md +0 -0
  15. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/__init__.py +0 -0
  16. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/adapters_ternary.py +0 -0
  17. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
  18. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/colorbar_runtime.py +0 -0
  19. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/config_runtime.py +0 -0
  20. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/data_pipelines.py +0 -0
  21. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/figure.py +0 -0
  22. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/helper.py +0 -0
  23. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/interp_natural_neighbor.py +0 -0
  24. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/layout_runtime.py +0 -0
  25. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/method_registry.py +0 -0
  26. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/preprocessor.py +0 -0
  27. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/preprocessor_runtime.py +0 -0
  28. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/profile_runtime.py +0 -0
  29. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/Figure/style_runtime.py +0 -0
  30. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/__init__.py +0 -0
  31. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cache_store.py +0 -0
  32. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
  33. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/2x1/rect.json +0 -0
  34. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/2x1/rect5x1.json +0 -0
  35. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +0 -0
  36. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/2x1/ternary.json +0 -0
  37. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +0 -0
  38. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/4x1/rect.json +0 -0
  39. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +0 -0
  40. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/4x1/ternary.json +0 -0
  41. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
  42. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/args.json +0 -0
  43. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/colors/colormaps.json +0 -0
  44. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/default/output.json +0 -0
  45. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
  46. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/gambit/2x1/rect_cmap.json +0 -0
  47. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/gambit/2x1/ternary.json +0 -0
  48. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +0 -0
  49. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
  50. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/icons/gambit.png +0 -0
  51. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/icons/gambit_small.png +0 -0
  52. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cards/style_preference.json +0 -0
  53. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/cli.py +0 -0
  54. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/client.py +0 -0
  55. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/config.py +0 -0
  56. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/core.py +0 -0
  57. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/core_assets.py +0 -0
  58. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/core_runtime.py +0 -0
  59. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/data_loader.py +0 -0
  60. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/data_loader_hdf5.py +0 -0
  61. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/data_loader_runtime.py +0 -0
  62. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/data_loader_summary.py +0 -0
  63. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/inner_func.py +0 -0
  64. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/memtrace.py +0 -0
  65. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/utils/__init__.py +0 -0
  66. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/utils/cmaps.py +0 -0
  67. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/utils/dataframes.py +0 -0
  68. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/utils/expression.py +0 -0
  69. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/utils/interpolator.py +0 -0
  70. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/jarvisplot/utils/pathing.py +0 -0
  71. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/setup.cfg +0 -0
  72. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/tests/test_data_loader_contracts.py +0 -0
  73. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/tests/test_docs_status_labels.py +0 -0
  74. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/tests/test_figure_contracts.py +0 -0
  75. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/tests/test_interpolator_contracts.py +0 -0
  76. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/tests/test_parse_data_metadata.py +0 -0
  77. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/tests/test_pathing_contracts.py +0 -0
  78. {jarvisplot-1.3.6 → jarvisplot-1.3.7}/tests/test_template_json.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JarvisPLOT
3
- Version: 1.3.6
3
+ Version: 1.3.7
4
4
  Summary: Jarvis-PLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JarvisPLOT
3
- Version: 1.3.6
3
+ Version: 1.3.7
4
4
  Summary: Jarvis-PLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -14,6 +14,8 @@ from .helper import _auto_clip, _mask_by_extend, voronoi_finite_polygons_2d, _cl
14
14
  from .profile_runtime import grid_profile_mesh
15
15
  from .interp_natural_neighbor import resolve_backend
16
16
 
17
+ DEFAULT_JP_SAMPLE_GRID = 500
18
+
17
19
 
18
20
  # —— Basic Adapter: Forward to the underlying Axes, merge default parameters, perform automatic clipping ——
19
21
 
@@ -207,7 +209,7 @@ class StdAxesAdapter:
207
209
  x,
208
210
  y,
209
211
  *,
210
- bin: int = 128,
212
+ bin: int = DEFAULT_JP_SAMPLE_GRID,
211
213
  nx: Optional[int] = None,
212
214
  ny: Optional[int] = None,
213
215
  xlim=None,
@@ -218,8 +220,8 @@ class StdAxesAdapter:
218
220
  if x.shape[0] != y.shape[0]:
219
221
  raise ValueError("x and y must have the same length")
220
222
 
221
- nx_eff = self._coerce_positive_int(nx if nx is not None else bin, 128)
222
- ny_eff = self._coerce_positive_int(ny if ny is not None else bin, 128)
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)
223
225
  x_norm = self._normalize_jpfield_axis("x", x, bounds=xlim)
224
226
  y_norm = self._normalize_jpfield_axis("y", y, bounds=ylim)
225
227
  xq = np.linspace(0.0, 1.0, nx_eff)
@@ -234,7 +236,7 @@ class StdAxesAdapter:
234
236
  z,
235
237
  *,
236
238
  interp_method: str = "natural_neighbor",
237
- bin: int = 128,
239
+ bin: int = DEFAULT_JP_SAMPLE_GRID,
238
240
  nx: Optional[int] = None,
239
241
  ny: Optional[int] = None,
240
242
  xlim=None,
@@ -279,7 +281,7 @@ class StdAxesAdapter:
279
281
  x,
280
282
  y,
281
283
  *,
282
- bin: int = 128,
284
+ bin: int = DEFAULT_JP_SAMPLE_GRID,
283
285
  nx: Optional[int] = None,
284
286
  ny: Optional[int] = None,
285
287
  xlim=None,
@@ -301,8 +303,8 @@ class StdAxesAdapter:
301
303
  except Exception:
302
304
  ylim = None
303
305
 
304
- nx_eff = self._coerce_positive_int(nx if nx is not None else bin, 128)
305
- ny_eff = self._coerce_positive_int(ny if ny is not None else bin, 128)
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)
306
308
  xq = self._resolve_jpcontour_axis("x", x, npts=nx_eff, bounds=xlim)
307
309
  yq = self._resolve_jpcontour_axis("y", y, npts=ny_eff, bounds=ylim)
308
310
  return np.meshgrid(xq, yq)
@@ -314,7 +316,7 @@ class StdAxesAdapter:
314
316
  z,
315
317
  *,
316
318
  interp_method: str = "natural_neighbor",
317
- bin: int = 128,
319
+ bin: int = DEFAULT_JP_SAMPLE_GRID,
318
320
  nx: Optional[int] = None,
319
321
  ny: Optional[int] = None,
320
322
  xlim=None,
@@ -323,13 +325,19 @@ class StdAxesAdapter:
323
325
  diagnostics: bool = False,
324
326
  backend_options: Optional[dict[str, Any]] = None,
325
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
+ """
326
334
  x = np.asarray(x, dtype=float).reshape(-1)
327
335
  y = np.asarray(y, dtype=float).reshape(-1)
328
336
  z = np.asarray(z, dtype=float).reshape(-1)
329
337
  if x.shape[0] != y.shape[0] or x.shape[0] != z.shape[0]:
330
338
  raise ValueError("x, y, and z must have the same length")
331
339
 
332
- X, Y = self._build_jpcontour_grid(
340
+ X, Y, x_norm, y_norm = self._build_jpfield_grid(
333
341
  x,
334
342
  y,
335
343
  bin=bin,
@@ -345,8 +353,8 @@ class StdAxesAdapter:
345
353
  f"Unsupported jpcontour interpolation backend: {interp_method!r}"
346
354
  ) from exc
347
355
  Z = backend(
348
- x,
349
- y,
356
+ x_norm,
357
+ y_norm,
350
358
  z,
351
359
  X,
352
360
  Y,
@@ -489,7 +497,7 @@ class StdAxesAdapter:
489
497
  z,
490
498
  *args,
491
499
  interp_method: str = "natural_neighbor",
492
- bin: int = 128,
500
+ bin: int = DEFAULT_JP_SAMPLE_GRID,
493
501
  nx: Optional[int] = None,
494
502
  ny: Optional[int] = None,
495
503
  xlim=None,
@@ -499,7 +507,9 @@ class StdAxesAdapter:
499
507
  backend_options: Optional[dict[str, Any]] = None,
500
508
  **kwargs,
501
509
  ):
510
+ """Scattered-data contour plot rendered in axes-fraction coordinates."""
502
511
  kw = self._merge("contour", kwargs)
512
+ kw.pop("transform", None)
503
513
  X, Y, Z = self._interpolate_jpcontour_grid(
504
514
  x,
505
515
  y,
@@ -514,7 +524,14 @@ class StdAxesAdapter:
514
524
  diagnostics=diagnostics,
515
525
  backend_options=backend_options,
516
526
  )
517
- artists = self.ax.contour(X, Y, np.ma.masked_invalid(Z), *args, **kw)
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
+ )
518
535
  return _auto_clip(artists, self.ax, self._clip_path)
519
536
 
520
537
  def jpcontourf(
@@ -524,7 +541,7 @@ class StdAxesAdapter:
524
541
  z,
525
542
  *args,
526
543
  interp_method: str = "natural_neighbor",
527
- bin: int = 500,
544
+ bin: int = DEFAULT_JP_SAMPLE_GRID,
528
545
  nx: Optional[int] = None,
529
546
  ny: Optional[int] = None,
530
547
  xlim=None,
@@ -534,7 +551,9 @@ class StdAxesAdapter:
534
551
  backend_options: Optional[dict[str, Any]] = None,
535
552
  **kwargs,
536
553
  ):
554
+ """Filled scattered-data contour plot rendered in axes-fraction coordinates."""
537
555
  kw = self._merge("contourf", kwargs)
556
+ kw.pop("transform", None)
538
557
  X, Y, Z = self._interpolate_jpcontour_grid(
539
558
  x,
540
559
  y,
@@ -549,7 +568,14 @@ class StdAxesAdapter:
549
568
  diagnostics=diagnostics,
550
569
  backend_options=backend_options,
551
570
  )
552
- artists = self.ax.contourf(X, Y, np.ma.masked_invalid(Z), *args, **kw)
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
+ )
553
579
  return _auto_clip(artists, self.ax, self._clip_path)
554
580
 
555
581
  def jpfield(
@@ -559,7 +585,7 @@ class StdAxesAdapter:
559
585
  z,
560
586
  *args,
561
587
  interp_method: str = "natural_neighbor",
562
- bin: int = 128,
588
+ bin: int = DEFAULT_JP_SAMPLE_GRID,
563
589
  nx: Optional[int] = None,
564
590
  ny: Optional[int] = None,
565
591
  xlim=None,
@@ -534,6 +534,8 @@ class NaturalNeighborExactInterpolator:
534
534
  self._hull_polygon = None
535
535
  self.diagnostics.degenerate_input = True
536
536
  _warn("natural_neighbor: convex hull is degenerate")
537
+ self.diagnostics.interpolator_ready = False
538
+ return
537
539
  else:
538
540
  self._hull_polygon = None
539
541
  self.diagnostics.degenerate_input = True
@@ -541,6 +543,8 @@ class NaturalNeighborExactInterpolator:
541
543
  self._hull_polygon = None
542
544
  self.diagnostics.degenerate_input = True
543
545
  _warn(f"natural_neighbor: convex hull construction failed: {exc}")
546
+ self.diagnostics.interpolator_ready = False
547
+ return
544
548
 
545
549
  try:
546
550
  if coords.shape[0] >= 3:
@@ -48,7 +48,7 @@ def _coerce_positive_int(value, default: int) -> int:
48
48
 
49
49
 
50
50
  def _resolve_interp_grid_size(interp_cfg: dict | None, x_size: int, y_size: int) -> tuple[int, int]:
51
- default_n = max(64, int(round(np.sqrt(max(int(x_size), int(y_size), 1)) * 4.0)))
51
+ default_n = 500
52
52
  if not isinstance(interp_cfg, dict):
53
53
  return default_n, default_n
54
54
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "JarvisPLOT"
7
- version = "1.3.6"
7
+ version = "1.3.7"
8
8
  description = "Jarvis-PLOT: YAML-driven plotting engine"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -186,7 +186,8 @@ def test_exact_backend_returns_nan_for_collinear_input():
186
186
  X = np.array([[1.0]])
187
187
  Y = np.array([[0.0]])
188
188
 
189
- Z = natural_neighbor_exact_interpolate(x, y, z, X, Y)
189
+ with pytest.warns(RuntimeWarning):
190
+ Z = natural_neighbor_exact_interpolate(x, y, z, X, Y)
190
191
 
191
192
  assert np.isnan(Z[0, 0])
192
193
  diag = natural_neighbor_exact_interpolate.last_diagnostics
@@ -86,6 +86,47 @@ def test_jpfield_returns_quadmesh():
86
86
  plt.close(fig)
87
87
 
88
88
 
89
+ @pytest.mark.parametrize(
90
+ "method_name, mpl_name",
91
+ [("jpcontour", "contour"), ("jpcontourf", "contourf"), ("jpfield", "pcolormesh")],
92
+ )
93
+ def test_default_jp_sample_grid_is_500_when_bin_is_omitted(monkeypatch, method_name, mpl_name):
94
+ fig, ax = plt.subplots()
95
+ adapter = StdAxesAdapter(ax)
96
+
97
+ backend_calls = {}
98
+
99
+ def fake_backend(
100
+ x,
101
+ y,
102
+ z,
103
+ X,
104
+ Y,
105
+ *,
106
+ nan_policy="strict",
107
+ diagnostics=False,
108
+ backend_options=None,
109
+ ):
110
+ backend_calls["grid_shape"] = X.shape
111
+ return np.asarray(X + Y, dtype=float)
112
+
113
+ monkeypatch.setattr(adapters_rect_mod, "resolve_backend", lambda name: fake_backend)
114
+ monkeypatch.setattr(adapter.ax, mpl_name, lambda *args, **kwargs: SimpleNamespace(tag=method_name))
115
+
116
+ x = np.array([0.0, 1.0, 0.0, 1.0])
117
+ y = np.array([0.0, 0.0, 1.0, 1.0])
118
+ z = np.array([0.0, 1.0, 1.0, 2.0])
119
+
120
+ if method_name == "jpfield":
121
+ out = getattr(adapter, method_name)(x, y, z, cmap="viridis")
122
+ else:
123
+ out = getattr(adapter, method_name)(x, y, z, levels=3, cmap="viridis")
124
+
125
+ assert out.tag == method_name
126
+ assert backend_calls["grid_shape"] == (500, 500)
127
+ plt.close(fig)
128
+
129
+
89
130
  @pytest.mark.parametrize("method_name", ["jpcontour", "jpcontourf"])
90
131
  def test_render_layer_dispatches_jpcontour_methods_with_scattered_inputs(monkeypatch, method_name):
91
132
  df = pd.DataFrame(
@@ -163,6 +204,8 @@ def test_jpcontour_strips_interp_kwargs_and_preserves_masked_nan_grid(monkeypatc
163
204
  diagnostics=False,
164
205
  backend_options=None,
165
206
  ):
207
+ backend_calls["x"] = np.asarray(x, dtype=float)
208
+ backend_calls["y"] = np.asarray(y, dtype=float)
166
209
  backend_calls["nan_policy"] = nan_policy
167
210
  backend_calls["diagnostics"] = diagnostics
168
211
  backend_calls["backend_options"] = backend_options
@@ -184,8 +227,8 @@ def test_jpcontour_strips_interp_kwargs_and_preserves_masked_nan_grid(monkeypatc
184
227
 
185
228
  monkeypatch.setattr(adapter.ax, mpl_name, recorder)
186
229
 
187
- x = np.array([0.0, 1.0, 0.0, 1.0])
188
- y = np.array([0.0, 0.0, 1.0, 1.0])
230
+ x = np.array([0.0, 1.0, 2.0, 2.0])
231
+ y = np.array([0.0, 0.5, 1.5, 2.0])
189
232
  z = np.array([0.0, 1.0, 1.0, 2.0])
190
233
 
191
234
  out = getattr(adapter, method_name)(
@@ -199,8 +242,8 @@ def test_jpcontour_strips_interp_kwargs_and_preserves_masked_nan_grid(monkeypatc
199
242
  bin=6,
200
243
  nx=4,
201
244
  ny=5,
202
- xlim=(-1.0, 1.0),
203
- ylim=(-2.0, 2.0),
245
+ xlim=(0.0, 2.0),
246
+ ylim=(0.0, 2.0),
204
247
  nan_policy="strict",
205
248
  diagnostics=True,
206
249
  backend_options={"vertex_tol": 1.0e-6},
@@ -211,10 +254,15 @@ def test_jpcontour_strips_interp_kwargs_and_preserves_masked_nan_grid(monkeypatc
211
254
  assert backend_calls["diagnostics"] is True
212
255
  assert backend_calls["backend_options"] == {"vertex_tol": 1.0e-6}
213
256
  assert backend_calls["grid_shape"] == (5, 4)
214
- assert backend_calls["x_range"] == (-1.0, 1.0)
215
- assert backend_calls["y_range"] == (-2.0, 2.0)
257
+ assert backend_calls["x_range"] == (0.0, 1.0)
258
+ assert backend_calls["y_range"] == (0.0, 1.0)
259
+ np.testing.assert_allclose(backend_calls["x"], np.array([0.0, 0.5, 1.0, 1.0]))
260
+ np.testing.assert_allclose(backend_calls["y"], np.array([0.0, 0.25, 0.75, 1.0]))
216
261
 
217
- assert mpl_calls["kwargs"] == {"levels": 3, "colors": "k", "linewidths": 2.0}
262
+ assert mpl_calls["kwargs"]["levels"] == 3
263
+ assert mpl_calls["kwargs"]["colors"] == "k"
264
+ assert mpl_calls["kwargs"]["linewidths"] == 2.0
265
+ assert mpl_calls["kwargs"]["transform"] is adapter.ax.transAxes
218
266
  z_arg = mpl_calls["args"][2]
219
267
  assert np.ma.isMaskedArray(z_arg)
220
268
  mask = np.ma.getmaskarray(z_arg)
@@ -224,6 +272,73 @@ def test_jpcontour_strips_interp_kwargs_and_preserves_masked_nan_grid(monkeypatc
224
272
  plt.close(fig)
225
273
 
226
274
 
275
+ def test_render_layer_contourf_defaults_to_500_grid_when_interp_bin_missing(monkeypatch):
276
+ df = pd.DataFrame(
277
+ {
278
+ "x": [0.0, 1.0, 0.0, 1.0],
279
+ "y": [0.0, 0.0, 1.0, 1.0],
280
+ "z": [0.0, 1.0, 1.0, 2.0],
281
+ }
282
+ )
283
+
284
+ fig = SimpleNamespace(
285
+ logger=SimpleNamespace(debug=lambda *args, **kwargs: None, warning=lambda *args, **kwargs: None),
286
+ style={"contourf": {"interp": {"method": "natural_neighbor"}, "levels": 3}},
287
+ _ensure_pandas_data=lambda data, reason="render": data,
288
+ _eval_series=lambda data, spec: np.asarray(data[spec["expr"]]),
289
+ )
290
+
291
+ ax = SimpleNamespace(
292
+ _type="rect",
293
+ get_xscale=lambda: "linear",
294
+ get_yscale=lambda: "linear",
295
+ get_xlim=lambda: (0.0, 1.0),
296
+ get_ylim=lambda: (0.0, 1.0),
297
+ )
298
+
299
+ backend_calls = {}
300
+
301
+ def fake_backend(
302
+ x,
303
+ y,
304
+ z,
305
+ X,
306
+ Y,
307
+ *,
308
+ nan_policy="strict",
309
+ diagnostics=False,
310
+ backend_options=None,
311
+ ):
312
+ backend_calls["grid_shape"] = X.shape
313
+ return X + Y
314
+
315
+ method_calls = {}
316
+
317
+ def dummy_method(*args, **kwargs):
318
+ method_calls["kwargs"] = dict(kwargs)
319
+ return "ok"
320
+
321
+ monkeypatch.setattr(layer_runtime_mod, "resolve_backend", lambda name: fake_backend)
322
+ monkeypatch.setattr(layer_runtime_mod, "resolve_callable", lambda *args, **kwargs: (dummy_method, None))
323
+ monkeypatch.setattr(layer_runtime_mod, "collect_and_attach_colorbar", lambda *args, **kwargs: args[1])
324
+
325
+ out = layer_runtime_mod.render_layer(
326
+ fig,
327
+ ax,
328
+ {
329
+ "name": "contourf-layer",
330
+ "method": "contourf",
331
+ "data": df,
332
+ "coor": {"x": {"expr": "x"}, "y": {"expr": "y"}, "z": {"expr": "z"}},
333
+ "style": {},
334
+ },
335
+ )
336
+
337
+ assert out == "ok"
338
+ assert backend_calls["grid_shape"] == (500, 500)
339
+ assert method_calls["kwargs"]["levels"] == 3
340
+
341
+
227
342
  def test_jpfield_strips_interp_kwargs_and_preserves_masked_nan_grid(monkeypatch):
228
343
  fig, ax = plt.subplots()
229
344
  adapter = StdAxesAdapter(ax)
@@ -457,6 +572,7 @@ def test_ternary_jpcontourf_accepts_left_right_bottom(monkeypatch):
457
572
  np.testing.assert_allclose(backend_calls["y"], expected_y)
458
573
  np.testing.assert_allclose(backend_calls["z"], z)
459
574
  assert mpl_calls["kwargs"]["levels"] == 4
575
+ assert mpl_calls["kwargs"]["transform"] is adapter.ax.transAxes
460
576
  plt.close(fig)
461
577
 
462
578
 
@@ -626,4 +742,5 @@ def test_jpcontour_strips_interpolation_kwargs_from_mpl_call(monkeypatch):
626
742
  assert "ylim" not in mpl_calls["kwargs"]
627
743
  assert "nan_policy" not in mpl_calls["kwargs"]
628
744
  assert "backend_options" not in mpl_calls["kwargs"]
745
+ assert mpl_calls["kwargs"]["transform"] is adapter.ax.transAxes
629
746
  plt.close(fig)
File without changes
File without changes
File without changes