jarvisplot 1.4.0__tar.gz → 1.4.1__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 (99) hide show
  1. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/JarvisPLOT.egg-info/PKG-INFO +1 -1
  2. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/PKG-INFO +1 -1
  3. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/colorbar_runtime.py +2 -0
  4. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/figure.py +6 -0
  5. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/layer_runtime.py +119 -6
  6. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/flowchart.py +10 -3
  7. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/pyproject.toml +1 -1
  8. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_figure_contracts.py +135 -0
  9. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/JarvisPLOT.egg-info/SOURCES.txt +0 -0
  10. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/JarvisPLOT.egg-info/dependency_links.txt +0 -0
  11. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/JarvisPLOT.egg-info/entry_points.txt +0 -0
  12. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/JarvisPLOT.egg-info/requires.txt +0 -0
  13. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/JarvisPLOT.egg-info/top_level.txt +0 -0
  14. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/README.md +0 -0
  15. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/__init__.py +0 -0
  16. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/adapters_rect.py +0 -0
  17. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/adapters_ternary.py +0 -0
  18. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
  19. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/config_runtime.py +0 -0
  20. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/data_pipelines.py +0 -0
  21. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/density_cell_runtime.py +0 -0
  22. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/dynesty_runtime.py +0 -0
  23. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/figure_types.py +0 -0
  24. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/helper.py +0 -0
  25. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/interp_2d_runtime.py +0 -0
  26. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/interp_natural_neighbor.py +0 -0
  27. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/interp_natural_neighbor_exact.py +0 -0
  28. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/layout_runtime.py +0 -0
  29. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/method_registry.py +0 -0
  30. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/posterior_density_runtime.py +0 -0
  31. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/posterior_hpd.py +0 -0
  32. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/posterior_mesh.py +0 -0
  33. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/preprocessor.py +0 -0
  34. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/preprocessor_runtime.py +0 -0
  35. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/profile_runtime.py +0 -0
  36. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/style_runtime.py +0 -0
  37. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/__init__.py +0 -0
  38. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cache_store.py +0 -0
  39. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
  40. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/2x1/dynesty_runplot.json +0 -0
  41. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/2x1/rect.json +0 -0
  42. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/2x1/rect5x1.json +0 -0
  43. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +0 -0
  44. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/2x1/ternary.json +0 -0
  45. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +0 -0
  46. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/4x1/rect.json +0 -0
  47. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +0 -0
  48. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/4x1/ternary.json +0 -0
  49. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
  50. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/args.json +0 -0
  51. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/colors/colormaps.json +0 -0
  52. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/default/output.json +0 -0
  53. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/flowchart/default.json +0 -0
  54. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
  55. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/gambit/2x1/rect_cmap.json +0 -0
  56. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/gambit/2x1/ternary.json +0 -0
  57. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +0 -0
  58. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
  59. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/flowchart/JarvisHEP.png +0 -0
  60. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/flowchart/calculator.png +0 -0
  61. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/flowchart/inputfile.png +0 -0
  62. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/flowchart/opera.png +0 -0
  63. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/flowchart/outputfile.png +0 -0
  64. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/flowchart/sampler.png +0 -0
  65. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/gambit.png +0 -0
  66. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/gambit_small.png +0 -0
  67. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/style_preference.json +0 -0
  68. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cli.py +0 -0
  69. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/client.py +0 -0
  70. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/config.py +0 -0
  71. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/core.py +0 -0
  72. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/core_assets.py +0 -0
  73. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/core_runtime.py +0 -0
  74. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/data_loader.py +0 -0
  75. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/data_loader_hdf5.py +0 -0
  76. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/data_loader_runtime.py +0 -0
  77. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/data_loader_summary.py +0 -0
  78. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/inner_func.py +0 -0
  79. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/memtrace.py +0 -0
  80. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/utils/__init__.py +0 -0
  81. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/utils/cmaps.py +0 -0
  82. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/utils/dataframes.py +0 -0
  83. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/utils/expression.py +0 -0
  84. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/utils/interpolator.py +0 -0
  85. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/utils/pathing.py +0 -0
  86. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/setup.cfg +0 -0
  87. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_data_loader_contracts.py +0 -0
  88. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_density_cell_transform.py +0 -0
  89. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_docs_status_labels.py +0 -0
  90. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_figure_type_expander.py +0 -0
  91. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_flowchart_renderer.py +0 -0
  92. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_interp_2d_transform.py +0 -0
  93. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_interp_natural_neighbor.py +0 -0
  94. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_interpolator_contracts.py +0 -0
  95. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_jpcontour_methods.py +0 -0
  96. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_parse_data_metadata.py +0 -0
  97. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_pathing_contracts.py +0 -0
  98. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_posterior_hpd.py +0 -0
  99. {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_template_json.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JarvisPLOT
3
- Version: 1.4.0
3
+ Version: 1.4.1
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.4.0
3
+ Version: 1.4.1
4
4
  Summary: Jarvis-PLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -394,6 +394,8 @@ def collect_and_attach_colorbar(
394
394
  if not axc._cb.get("used"):
395
395
  frame = getattr(fig, "frame", {})
396
396
  color_cfg = axc_color_config(frame, cb_name)
397
+ if color_cfg.get("cmap") is None and style.get("cmap") is not None:
398
+ color_cfg["cmap"] = style.get("cmap")
397
399
  data_range = collect_layer_color_range(
398
400
  df,
399
401
  coor,
@@ -711,6 +711,7 @@ class Figure:
711
711
  read-only during the main render loop.
712
712
  """
713
713
  cb_ranges: dict[str, list] = {}
714
+ cb_style_cmaps: dict[str, object] = {}
714
715
 
715
716
  for ax, ly in self._render_queue:
716
717
  cb_name = ly.get("colorbar", "axc")
@@ -724,6 +725,9 @@ class Figure:
724
725
  if not layer_uses_color(style, coor, method_key):
725
726
  continue
726
727
 
728
+ if style.get("cmap") is not None:
729
+ cb_style_cmaps.setdefault(cb_name, style.get("cmap"))
730
+
727
731
  # Load data (pipeline cache hit on second call in render loop)
728
732
  runtime_load_layer_runtime_data(self, ly)
729
733
  df = ly.get("data")
@@ -742,6 +746,8 @@ class Figure:
742
746
  if axc_obj is None or not hasattr(axc_obj, "_cb"):
743
747
  continue
744
748
  color_cfg = axc_color_config(self.frame, cb_name)
749
+ if color_cfg.get("cmap") is None and cb_name in cb_style_cmaps:
750
+ color_cfg["cmap"] = cb_style_cmaps[cb_name]
745
751
  axc_obj._cb.update(
746
752
  precompute_colorbar_cb(color_cfg, ranges, logger=self.logger)
747
753
  )
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
 
7
7
  import numpy as np
8
8
  import pandas as pd
9
+ from matplotlib.path import Path as MplPath
9
10
 
10
11
  from .preprocessor_runtime import add_column, filter_df, sort_by
11
12
  from .profile_runtime import profiling
@@ -15,6 +16,117 @@ from .interp_natural_neighbor import resolve_backend
15
16
  from .dynesty_runtime import render_dynesty_runplot
16
17
  from .posterior_hpd import prepare_hpd_contour_style
17
18
 
19
+ _MISSING_CLIP_PATH = object()
20
+
21
+
22
+ def _clip_expr_inside(expr: str, point) -> bool:
23
+ px, py = float(point[0]), float(point[1])
24
+ env = {
25
+ "x": px,
26
+ "y": py,
27
+ "np": np,
28
+ "numpy": np,
29
+ "abs": abs,
30
+ "min": min,
31
+ "max": max,
32
+ }
33
+ try:
34
+ return bool(eval(expr, {"__builtins__": {}}, env))
35
+ except Exception as exc:
36
+ raise ValueError(f"layer clip_expr failed for {expr!r}: {exc}") from exc
37
+
38
+
39
+ def _clip_polygon_to_expr(poly, expr: str, *, iterations: int = 48):
40
+ """Clip a polygon to the region where expr is True in data coordinates."""
41
+ expr = str(expr or "").strip()
42
+ if not expr or not poly:
43
+ return poly
44
+
45
+ def inside(pt) -> bool:
46
+ return _clip_expr_inside(expr, pt)
47
+
48
+ def intersect(a, b):
49
+ pa = np.asarray(a, dtype=float)
50
+ pb = np.asarray(b, dtype=float)
51
+ a_inside = inside(pa)
52
+ lo = pa
53
+ hi = pb
54
+ for _ in range(iterations):
55
+ mid = 0.5 * (lo + hi)
56
+ if inside(mid) == a_inside:
57
+ lo = mid
58
+ else:
59
+ hi = mid
60
+ return tuple(0.5 * (lo + hi))
61
+
62
+ out = []
63
+ start = poly[-1]
64
+ start_inside = inside(start)
65
+ for end in poly:
66
+ end_inside = inside(end)
67
+ if end_inside:
68
+ if not start_inside:
69
+ out.append(intersect(start, end))
70
+ out.append(end)
71
+ elif start_inside:
72
+ out.append(intersect(start, end))
73
+ start = end
74
+ start_inside = end_inside
75
+ return out
76
+
77
+
78
+ def _layer_clip_path_from_style(ax, style: dict):
79
+ expr = style.pop("clip_expr", style.pop("clip_where", None))
80
+ if expr is None:
81
+ return None
82
+ raw_ax = getattr(ax, "ax", ax)
83
+ try:
84
+ x0, x1 = raw_ax.get_xlim()
85
+ y0, y1 = raw_ax.get_ylim()
86
+ except Exception as exc:
87
+ raise ValueError("layer clip_expr requires axes with data limits") from exc
88
+
89
+ xmin, xmax = sorted((float(x0), float(x1)))
90
+ ymin, ymax = sorted((float(y0), float(y1)))
91
+ rect = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
92
+ clipped = _clip_polygon_to_expr(rect, str(expr))
93
+ if len(clipped) < 3:
94
+ return MplPath([(xmin, ymin), (xmin, ymin)], [MplPath.MOVETO, MplPath.CLOSEPOLY])
95
+
96
+ vertices = list(clipped) + [tuple(clipped[0])]
97
+ codes = [MplPath.MOVETO] + [MplPath.LINETO] * (len(clipped) - 1) + [MplPath.CLOSEPOLY]
98
+ return MplPath(vertices, codes)
99
+
100
+
101
+ def _layer_clip_targets(ax):
102
+ raw_ax = getattr(ax, "ax", None)
103
+ targets = []
104
+ for obj in (ax, raw_ax):
105
+ if obj is not None and not any(obj is prev for prev in targets):
106
+ targets.append(obj)
107
+ return targets
108
+
109
+
110
+ def _call_with_layer_clip(ax, clip_path, func, *args, **kwargs):
111
+ if clip_path is None:
112
+ return func(*args, **kwargs)
113
+
114
+ states = []
115
+ for target in _layer_clip_targets(ax):
116
+ states.append((target, getattr(target, "_clip_path", _MISSING_CLIP_PATH)))
117
+ target._clip_path = clip_path
118
+ try:
119
+ return func(*args, **kwargs)
120
+ finally:
121
+ for target, previous in reversed(states):
122
+ if previous is _MISSING_CLIP_PATH:
123
+ try:
124
+ delattr(target, "_clip_path")
125
+ except Exception:
126
+ pass
127
+ else:
128
+ target._clip_path = previous
129
+
18
130
 
19
131
  def _resolve_csv_export_path(fig, target):
20
132
  if isinstance(target, dict):
@@ -664,6 +776,7 @@ def render_layer(fig, ax, layer_info):
664
776
  pass
665
777
  if layer_info.get("style", {}) is not None:
666
778
  style.update(layer_info.get("style", {}))
779
+ layer_clip_path = _layer_clip_path_from_style(ax, style)
667
780
 
668
781
  cb_name = layer_info.get("colorbar", "axc")
669
782
 
@@ -696,8 +809,8 @@ def render_layer(fig, ax, layer_info):
696
809
  required_keys=("z",),
697
810
  include_diagnostics=method_key in {"jpcontour", "jpcontourf"},
698
811
  )
699
- return method(**jp_kwargs)
700
- return method(**style)
812
+ return _call_with_layer_clip(ax, layer_clip_path, method, **jp_kwargs)
813
+ return _call_with_layer_clip(ax, layer_clip_path, method, **style)
701
814
 
702
815
  elif getattr(ax, "_type", None) == "rect":
703
816
  df = fig._ensure_pandas_data(layer_info["data"], reason=f"render:{layer_info.get('name', '')}")
@@ -724,7 +837,7 @@ def render_layer(fig, ax, layer_info):
724
837
  for kk, vv in coor.items():
725
838
  style[kk] = fig._eval_series(df, vv)
726
839
 
727
- return method(**style)
840
+ return _call_with_layer_clip(ax, layer_clip_path, method, **style)
728
841
  if method_key in {"jpcontour", "jpcontourf", "jpfield"}:
729
842
  jp_kwargs = _prepare_jpcontour_style(
730
843
  fig,
@@ -737,7 +850,7 @@ def render_layer(fig, ax, layer_info):
737
850
  required_keys=("x", "y", "z"),
738
851
  include_diagnostics=method_key in {"jpcontour", "jpcontourf"},
739
852
  )
740
- return method(**jp_kwargs)
853
+ return _call_with_layer_clip(ax, layer_clip_path, method, **jp_kwargs)
741
854
  if method_key in {"contour", "contourf"}:
742
855
  contour_coor = {}
743
856
  for kk in ("x", "y", "z"):
@@ -759,7 +872,7 @@ def render_layer(fig, ax, layer_info):
759
872
  contour_args, style = _prepare_contour_args(fig, ax.ax if hasattr(ax, "ax") else ax, method_key, style, contour_coor, df=df)
760
873
  if contour_args is None:
761
874
  return []
762
- return method(*contour_args, **style)
875
+ return _call_with_layer_clip(ax, layer_clip_path, method, *contour_args, **style)
763
876
  else:
764
877
  if not ({"x", "y"} <= set(coor.keys())):
765
878
  raise ValueError("Rectangular layer must define coordinates: {x,y} with exprs.")
@@ -777,7 +890,7 @@ def render_layer(fig, ax, layer_info):
777
890
  if method_key == "pcolormesh":
778
891
  style["__df__"] = df
779
892
  style.pop("interp", None)
780
- return method(**style)
893
+ return _call_with_layer_clip(ax, layer_clip_path, method, **style)
781
894
 
782
895
  else:
783
896
  raise ValueError(f"Axes '{ax}' has unknown _type='{getattr(ax, '_type', None)}'.")
@@ -276,13 +276,20 @@ class FlowchartRenderer:
276
276
  from shapely.geometry import Point, box
277
277
  from shapely.ops import unary_union
278
278
  x, y = item["pos"]
279
- circle1 = Point(x-0.15, y).buffer(0.15, resolution=128)
280
- circle2 = Point(x+0.15, y).buffer(0.15, resolution=128)
279
+
280
+ def point_buffer(point, radius, segments=128):
281
+ try:
282
+ return point.buffer(radius, quad_segs=segments)
283
+ except TypeError:
284
+ return point.buffer(radius, resolution=segments)
285
+
286
+ circle1 = point_buffer(Point(x-0.15, y), 0.15)
287
+ circle2 = point_buffer(Point(x+0.15, y), 0.15)
281
288
  square = box(x - 0.15, y - 0.15, x + 0.15, y + 0.15)
282
289
  merged = unary_union([circle1, circle2, square])
283
290
  xx, yy = merged.exterior.xy
284
291
  ax.fill(xx, yy, alpha=0.95, edgecolor="#F9F9F8", facecolor="#00D86B", linewidth=0.1, zorder=120)
285
- circle3 = Point(x-0.15, y).buffer(0.12, resolution=128)
292
+ circle3 = point_buffer(Point(x-0.15, y), 0.12)
286
293
  xx, yy = circle3.exterior.xy
287
294
  ax.fill(xx, yy, alpha=0.95, edgecolor="#FFFFFF", facecolor="#F9F9F8", linewidth=0.8, zorder=120)
288
295
  import matplotlib.patheffects as pe
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "JarvisPLOT"
7
- version = "1.4.0"
7
+ version = "1.4.1"
8
8
  description = "Jarvis-PLOT: YAML-driven plotting engine"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -224,6 +224,41 @@ def test_figure_set_alias_removed():
224
224
  assert not hasattr(Figure, "set")
225
225
 
226
226
 
227
+ def test_layer_clip_expr_builds_data_space_clip_path():
228
+ _, raw_ax = plt.subplots()
229
+ raw_ax.set_xlim(0.0, 10.0)
230
+ raw_ax.set_ylim(0.0, 10.0)
231
+ ax = SimpleNamespace(ax=raw_ax)
232
+ style = {"clip_expr": "x >= 2 * y", "cmap": "viridis"}
233
+
234
+ clip_path = layer_runtime_mod._layer_clip_path_from_style(ax, style)
235
+
236
+ assert "clip_expr" not in style
237
+ assert style["cmap"] == "viridis"
238
+ assert len(clip_path.vertices) >= 3
239
+ assert all(px >= 2 * py - 1e-10 for px, py in clip_path.vertices)
240
+ plt.close(raw_ax.figure)
241
+
242
+
243
+ def test_layer_clip_is_temporary_on_adapter_and_raw_axes():
244
+ raw_ax = SimpleNamespace(_clip_path="raw-original")
245
+ adapter = SimpleNamespace(ax=raw_ax, _clip_path="adapter-original")
246
+ clip_path = object()
247
+ observed = {}
248
+
249
+ def draw():
250
+ observed["adapter"] = adapter._clip_path
251
+ observed["raw"] = raw_ax._clip_path
252
+ return "drawn"
253
+
254
+ out = layer_runtime_mod._call_with_layer_clip(adapter, clip_path, draw)
255
+
256
+ assert out == "drawn"
257
+ assert observed == {"adapter": clip_path, "raw": clip_path}
258
+ assert adapter._clip_path == "adapter-original"
259
+ assert raw_ax._clip_path == "raw-original"
260
+
261
+
227
262
  def test_colorbar_contract_uses_frame_color_config():
228
263
  class DummyColorbarAxis:
229
264
  def __init__(self):
@@ -259,6 +294,106 @@ def test_colorbar_contract_uses_frame_color_config():
259
294
  assert fig.axc._cb["vmax"] == 10.0
260
295
 
261
296
 
297
+ def test_colorbar_contract_falls_back_to_layer_cmap_when_frame_omits_cmap():
298
+ class DummyColorbarAxis:
299
+ def __init__(self):
300
+ self._cb = {
301
+ "cmap": None,
302
+ "vmin": None,
303
+ "vmax": None,
304
+ "norm": None,
305
+ "levels": None,
306
+ "used": False,
307
+ }
308
+
309
+ fig = SimpleNamespace(
310
+ axes={"axc": DummyColorbarAxis()},
311
+ frame={"axc": {"color": {"scale": "linear"}}},
312
+ logger=_logger(),
313
+ )
314
+
315
+ out = collect_and_attach_colorbar(
316
+ fig,
317
+ style={"cmap": "jarvis_rainbow2_r"},
318
+ coor={"z": {"expr": "z"}},
319
+ method_key="voronoi",
320
+ df=pd.DataFrame({"z": [1, 2, 5]}),
321
+ )
322
+
323
+ assert out["cmap"] == "jarvis_rainbow2_r"
324
+ assert fig.axes["axc"]._cb["cmap"] == "jarvis_rainbow2_r"
325
+
326
+
327
+ def test_prescan_colorbar_uses_layer_cmap_when_frame_omits_cmap():
328
+ class DummyColorbarAxis:
329
+ def __init__(self):
330
+ self._cb = {
331
+ "cmap": None,
332
+ "vmin": None,
333
+ "vmax": None,
334
+ "norm": None,
335
+ "levels": None,
336
+ "used": False,
337
+ }
338
+
339
+ fig = Figure()
340
+ fig.logger = _logger()
341
+ fig.frame = {"axc": {"color": {"scale": "linear"}}}
342
+ fig.axes = {"axc": DummyColorbarAxis()}
343
+ layer = {
344
+ "name": "colored_voronoi",
345
+ "method": "voronoi",
346
+ "style": {"cmap": "jarvis_rainbow2_r"},
347
+ "coor": {"z": {"expr": "z"}},
348
+ "colorbar": "axc",
349
+ "data": pd.DataFrame({"z": [1, 2, 5]}),
350
+ "data_loaded": True,
351
+ "layer_spec": {},
352
+ "source_refs": [],
353
+ "share_name": None,
354
+ }
355
+ fig._render_queue = [(None, layer)]
356
+
357
+ fig._prescan_colorbar_ranges()
358
+
359
+ assert fig.axes["axc"]._cb["cmap"] == "jarvis_rainbow2_r"
360
+
361
+
362
+ def test_prescan_colorbar_frame_cmap_overrides_layer_cmap():
363
+ class DummyColorbarAxis:
364
+ def __init__(self):
365
+ self._cb = {
366
+ "cmap": None,
367
+ "vmin": None,
368
+ "vmax": None,
369
+ "norm": None,
370
+ "levels": None,
371
+ "used": False,
372
+ }
373
+
374
+ fig = Figure()
375
+ fig.logger = _logger()
376
+ fig.frame = {"axc": {"color": {"scale": "linear", "cmap": "viridis"}}}
377
+ fig.axes = {"axc": DummyColorbarAxis()}
378
+ layer = {
379
+ "name": "colored_voronoi",
380
+ "method": "voronoi",
381
+ "style": {"cmap": "jarvis_rainbow2_r"},
382
+ "coor": {"z": {"expr": "z"}},
383
+ "colorbar": "axc",
384
+ "data": pd.DataFrame({"z": [1, 2, 5]}),
385
+ "data_loaded": True,
386
+ "layer_spec": {},
387
+ "source_refs": [],
388
+ "share_name": None,
389
+ }
390
+ fig._render_queue = [(None, layer)]
391
+
392
+ fig._prescan_colorbar_ranges()
393
+
394
+ assert fig.axes["axc"]._cb["cmap"] == "viridis"
395
+
396
+
262
397
  def test_colorbar_attachment_skips_plain_scatter_without_color_channel():
263
398
  class DummyColorbarAxis:
264
399
  def __init__(self):
File without changes
File without changes
File without changes