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.
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/JarvisPLOT.egg-info/PKG-INFO +1 -1
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/PKG-INFO +1 -1
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/colorbar_runtime.py +2 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/figure.py +6 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/layer_runtime.py +119 -6
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/flowchart.py +10 -3
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/pyproject.toml +1 -1
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_figure_contracts.py +135 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/JarvisPLOT.egg-info/SOURCES.txt +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/JarvisPLOT.egg-info/dependency_links.txt +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/JarvisPLOT.egg-info/entry_points.txt +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/JarvisPLOT.egg-info/requires.txt +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/JarvisPLOT.egg-info/top_level.txt +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/README.md +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/__init__.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/adapters_rect.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/adapters_ternary.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/config_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/data_pipelines.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/density_cell_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/dynesty_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/figure_types.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/helper.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/interp_2d_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/interp_natural_neighbor.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/interp_natural_neighbor_exact.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/layout_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/method_registry.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/posterior_density_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/posterior_hpd.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/posterior_mesh.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/preprocessor.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/preprocessor_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/profile_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/Figure/style_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/__init__.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cache_store.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/2x1/dynesty_runplot.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/2x1/rect.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/2x1/rect5x1.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/2x1/ternary.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/4x1/rect.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/4x1/ternary.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/args.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/colors/colormaps.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/default/output.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/flowchart/default.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/gambit/2x1/rect_cmap.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/gambit/2x1/ternary.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/flowchart/JarvisHEP.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/flowchart/calculator.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/flowchart/inputfile.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/flowchart/opera.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/flowchart/outputfile.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/flowchart/sampler.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/gambit.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/icons/gambit_small.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cards/style_preference.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/cli.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/client.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/config.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/core.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/core_assets.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/core_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/data_loader.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/data_loader_hdf5.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/data_loader_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/data_loader_summary.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/inner_func.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/memtrace.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/utils/__init__.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/utils/cmaps.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/utils/dataframes.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/utils/expression.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/utils/interpolator.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/jarvisplot/utils/pathing.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/setup.cfg +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_data_loader_contracts.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_density_cell_transform.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_docs_status_labels.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_figure_type_expander.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_flowchart_renderer.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_interp_2d_transform.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_interp_natural_neighbor.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_interpolator_contracts.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_jpcontour_methods.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_parse_data_metadata.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_pathing_contracts.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_posterior_hpd.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.1}/tests/test_template_json.py +0 -0
|
@@ -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
|
|
700
|
-
return method
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
280
|
-
|
|
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)
|
|
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
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|