jarvisplot 1.4.0__tar.gz → 1.4.2__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.2}/JarvisPLOT.egg-info/PKG-INFO +1 -1
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/JarvisPLOT.egg-info/SOURCES.txt +1 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/PKG-INFO +1 -1
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/colorbar_runtime.py +2 -0
- jarvisplot-1.4.2/jarvisplot/Figure/design_runtime.py +137 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/figure.py +9 -10
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/layer_runtime.py +119 -6
- {jarvisplot-1.4.0/jarvisplot/cards/a4paper/4x1 → jarvisplot-1.4.2/jarvisplot/cards/a4paper/2x1}/rect.json +4 -0
- {jarvisplot-1.4.0/jarvisplot/cards/gambit → jarvisplot-1.4.2/jarvisplot/cards/a4paper}/2x1/ternary.json +3 -0
- {jarvisplot-1.4.0/jarvisplot/cards/a4paper/4x1 → jarvisplot-1.4.2/jarvisplot/cards/gambit/2x1}/ternary.json +17 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/flowchart.py +10 -3
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/pyproject.toml +1 -1
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/tests/test_figure_contracts.py +135 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/JarvisPLOT.egg-info/dependency_links.txt +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/JarvisPLOT.egg-info/entry_points.txt +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/JarvisPLOT.egg-info/requires.txt +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/JarvisPLOT.egg-info/top_level.txt +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/README.md +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/__init__.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/adapters_rect.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/adapters_ternary.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/config_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/data_pipelines.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/density_cell_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/dynesty_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/figure_types.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/helper.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/interp_2d_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/interp_natural_neighbor.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/interp_natural_neighbor_exact.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/layout_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/method_registry.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/posterior_density_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/posterior_hpd.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/posterior_mesh.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/preprocessor.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/preprocessor_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/profile_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/Figure/style_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/__init__.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cache_store.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/a4paper/2x1/dynesty_runplot.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/a4paper/2x1/rect5x1.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +0 -0
- {jarvisplot-1.4.0/jarvisplot/cards/a4paper/2x1 → jarvisplot-1.4.2/jarvisplot/cards/a4paper/4x1}/rect.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +0 -0
- {jarvisplot-1.4.0/jarvisplot/cards/a4paper/2x1 → jarvisplot-1.4.2/jarvisplot/cards/a4paper/4x1}/ternary.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/args.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/colors/colormaps.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/default/output.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/flowchart/default.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/gambit/2x1/rect_cmap.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/icons/flowchart/JarvisHEP.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/icons/flowchart/calculator.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/icons/flowchart/inputfile.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/icons/flowchart/opera.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/icons/flowchart/outputfile.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/icons/flowchart/sampler.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/icons/gambit.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/icons/gambit_small.png +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cards/style_preference.json +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/cli.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/client.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/config.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/core.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/core_assets.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/core_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/data_loader.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/data_loader_hdf5.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/data_loader_runtime.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/data_loader_summary.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/inner_func.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/memtrace.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/utils/__init__.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/utils/cmaps.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/utils/dataframes.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/utils/expression.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/utils/interpolator.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/jarvisplot/utils/pathing.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/setup.cfg +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/tests/test_data_loader_contracts.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/tests/test_density_cell_transform.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/tests/test_docs_status_labels.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/tests/test_figure_type_expander.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/tests/test_flowchart_renderer.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/tests/test_interp_2d_transform.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/tests/test_interp_natural_neighbor.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/tests/test_interpolator_contracts.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/tests/test_jpcontour_methods.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/tests/test_parse_data_metadata.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/tests/test_pathing_contracts.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/tests/test_posterior_hpd.py +0 -0
- {jarvisplot-1.4.0 → jarvisplot-1.4.2}/tests/test_template_json.py +0 -0
|
@@ -28,6 +28,7 @@ jarvisplot/Figure/colorbar_runtime.py
|
|
|
28
28
|
jarvisplot/Figure/config_runtime.py
|
|
29
29
|
jarvisplot/Figure/data_pipelines.py
|
|
30
30
|
jarvisplot/Figure/density_cell_runtime.py
|
|
31
|
+
jarvisplot/Figure/design_runtime.py
|
|
31
32
|
jarvisplot/Figure/dynesty_runtime.py
|
|
32
33
|
jarvisplot/Figure/figure.py
|
|
33
34
|
jarvisplot/Figure/figure_types.py
|
|
@@ -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,
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Design-reference debug overlay for Jarvis-PLOT figures.
|
|
3
|
+
|
|
4
|
+
When a figure is rendered with ``debug: true`` in the YAML, this module draws a
|
|
5
|
+
dimension overlay on top of the finished figure: every axes defined by the style
|
|
6
|
+
card is outlined and annotated with
|
|
7
|
+
|
|
8
|
+
- its name (``ax`` / ``axc`` / ``axtri`` / ``ax0`` ...),
|
|
9
|
+
- its position rect ``[left, bottom, width, height]`` in figure fractions
|
|
10
|
+
(exactly the ``Frame.axes.<name>.rect`` value from the style JSON card), and
|
|
11
|
+
- its width / height converted to centimetres from ``figure.figsize``.
|
|
12
|
+
|
|
13
|
+
The primary axes additionally gets margin dimension lines (left / top / bottom
|
|
14
|
+
insets), and colorbar axes get a gap dimension to the primary axes.
|
|
15
|
+
|
|
16
|
+
The overlay never participates in data rendering; it is a read-only annotation
|
|
17
|
+
layer added just before the figure is saved. All drawing is wrapped so a
|
|
18
|
+
failure here can never break a normal plot.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import matplotlib as mpl
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# overlay palette
|
|
27
|
+
_DIM = "#0A7FB6" # blue dimension lines + labels
|
|
28
|
+
_BOX = "#111111" # axes outline
|
|
29
|
+
_NAME = "#1A237E" # axes name + rect text
|
|
30
|
+
_MARGIN = "#FF3FA4" # pink figure border / caption
|
|
31
|
+
|
|
32
|
+
# order in which we look for the "primary" axes to fully dimension
|
|
33
|
+
_PRIMARY_ORDER = ("ax", "ax0", "ax1", "axtri")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _raw(ax):
|
|
37
|
+
"""Return the underlying matplotlib Axes for an adapter or raw Axes."""
|
|
38
|
+
return getattr(ax, "ax", ax)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _position(ax):
|
|
42
|
+
try:
|
|
43
|
+
bb = _raw(ax).get_position()
|
|
44
|
+
return float(bb.x0), float(bb.y0), float(bb.width), float(bb.height)
|
|
45
|
+
except Exception:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def draw_design_reference(fig) -> None:
|
|
50
|
+
"""Draw the dimension overlay onto ``fig`` (a JarvisPLOT Figure wrapper)."""
|
|
51
|
+
try:
|
|
52
|
+
_draw(fig)
|
|
53
|
+
except Exception as exc: # never break a real plot because of the overlay
|
|
54
|
+
logger = getattr(fig, "logger", None)
|
|
55
|
+
if logger is not None:
|
|
56
|
+
try:
|
|
57
|
+
logger.debug(f"design-reference overlay skipped: {exc}")
|
|
58
|
+
except Exception:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _draw(fig) -> None:
|
|
63
|
+
F = fig.fig
|
|
64
|
+
w_in, h_in = (float(v) for v in F.get_size_inches())
|
|
65
|
+
w_cm, h_cm = w_in * 2.54, h_in * 2.54
|
|
66
|
+
|
|
67
|
+
ov = F.add_axes([0.0, 0.0, 1.0, 1.0], zorder=10_000)
|
|
68
|
+
ov.set_xlim(0.0, 1.0)
|
|
69
|
+
ov.set_ylim(0.0, 1.0)
|
|
70
|
+
ov.set_axis_off()
|
|
71
|
+
ov.patch.set_alpha(0.0)
|
|
72
|
+
|
|
73
|
+
def label(x, y, text, *, color=_DIM, size=5.0, ha="center", va="center",
|
|
74
|
+
rotation=0, weight="normal", family="monospace", boxed=True):
|
|
75
|
+
bbox = dict(boxstyle="square,pad=0.12", fc="white", ec="none", alpha=0.82) if boxed else None
|
|
76
|
+
ov.text(x, y, text, color=color, fontsize=size, ha=ha, va=va,
|
|
77
|
+
rotation=rotation, fontweight=weight, family=family, bbox=bbox,
|
|
78
|
+
zorder=10_002, clip_on=False)
|
|
79
|
+
|
|
80
|
+
def dim(x0, y0, x1, y1, text, *, vertical=False):
|
|
81
|
+
ov.annotate("", xy=(x1, y1), xytext=(x0, y0),
|
|
82
|
+
arrowprops=dict(arrowstyle="<|-|>", color=_DIM, lw=0.7,
|
|
83
|
+
shrinkA=0, shrinkB=0, mutation_scale=6),
|
|
84
|
+
zorder=10_001)
|
|
85
|
+
label((x0 + x1) / 2.0, (y0 + y1) / 2.0, text,
|
|
86
|
+
rotation=90 if vertical else 0)
|
|
87
|
+
|
|
88
|
+
# figure border + size caption
|
|
89
|
+
ov.add_patch(mpl.patches.Rectangle((0, 0), 1, 1, fill=False, ec=_MARGIN,
|
|
90
|
+
lw=0.9, zorder=10_001))
|
|
91
|
+
label(0.5, 0.995, f"design reference · figure {w_cm:.2f} cm × {h_cm:.2f} cm",
|
|
92
|
+
color=_MARGIN, size=6.0, va="top", weight="bold", family="DejaVu Sans")
|
|
93
|
+
|
|
94
|
+
# total figure height marker on the far left
|
|
95
|
+
dim(0.012, 0.0, 0.012, 1.0, f"{h_cm:.2f} cm", vertical=True)
|
|
96
|
+
|
|
97
|
+
# pick the primary axes to fully dimension
|
|
98
|
+
primary = next((n for n in _PRIMARY_ORDER if n in fig.axes), None)
|
|
99
|
+
primary_pos = _position(fig.axes[primary]) if primary else None
|
|
100
|
+
|
|
101
|
+
for name, ax in fig.axes.items():
|
|
102
|
+
pos = _position(ax)
|
|
103
|
+
if pos is None:
|
|
104
|
+
continue
|
|
105
|
+
l, b, w, h = pos
|
|
106
|
+
|
|
107
|
+
# outline only, so the real plot underneath keeps its true colors
|
|
108
|
+
ov.add_patch(mpl.patches.Rectangle((l, b), w, h, fill=False, ec=_BOX,
|
|
109
|
+
lw=0.9, zorder=10_001))
|
|
110
|
+
|
|
111
|
+
# name + rect + cm sizes — anchored so the text stays inside the figure
|
|
112
|
+
if l + w / 2.0 > 0.6: # axes sits in the right half
|
|
113
|
+
tx, ha = min(l + w, 0.992), "right"
|
|
114
|
+
else:
|
|
115
|
+
tx, ha = max(l + 0.012, 0.008), "left"
|
|
116
|
+
ty = min(b + h - 0.015, 0.93)
|
|
117
|
+
label(tx, ty, name, color=_NAME, size=7.0, ha=ha, va="top",
|
|
118
|
+
weight="bold", boxed=True)
|
|
119
|
+
label(tx, ty - 0.057,
|
|
120
|
+
f"[{l:.3f}, {b:.3f}, {w:.3f}, {h:.3f}]\n"
|
|
121
|
+
f"width: {w * w_cm:.3f} cm height: {h * h_cm:.3f} cm",
|
|
122
|
+
color=_NAME, size=4.6, ha=ha, va="top", boxed=True)
|
|
123
|
+
|
|
124
|
+
if name == primary:
|
|
125
|
+
yt = b + h
|
|
126
|
+
# left inset (figure left edge → axes left edge), drawn along the top edge
|
|
127
|
+
dim(0.0, yt, l, yt, f"{l * w_cm:.3f} cm")
|
|
128
|
+
# top inset (axes top edge → figure top)
|
|
129
|
+
dim(l, yt, l, 1.0, f"{(1.0 - yt) * h_cm:.3f} cm", vertical=True)
|
|
130
|
+
# bottom inset (figure bottom → axes bottom edge)
|
|
131
|
+
dim(l, 0.0, l, b, f"{b * h_cm:.3f} cm", vertical=True)
|
|
132
|
+
elif name.startswith("axc") and primary_pos is not None:
|
|
133
|
+
# gap between the primary axes' right edge and this colorbar's left edge
|
|
134
|
+
pl, pb, pw, ph = primary_pos
|
|
135
|
+
ygap = b + h / 2.0
|
|
136
|
+
if l > pl + pw:
|
|
137
|
+
dim(pl + pw, ygap, l, ygap, f"{(l - (pl + pw)) * w_cm:.3f} cm")
|
|
@@ -13,6 +13,7 @@ from .adapters_ternary import TernaryAxesAdapter
|
|
|
13
13
|
from .method_registry import resolve_callable
|
|
14
14
|
from .style_runtime import resolve_style_bundle_payload
|
|
15
15
|
from .config_runtime import apply_figure_config
|
|
16
|
+
from .design_runtime import draw_design_reference
|
|
16
17
|
from .layer_runtime import (
|
|
17
18
|
load_layer_runtime_data as runtime_load_layer_runtime_data,
|
|
18
19
|
release_layer_runtime_data as runtime_release_layer_runtime_data,
|
|
@@ -711,6 +712,7 @@ class Figure:
|
|
|
711
712
|
read-only during the main render loop.
|
|
712
713
|
"""
|
|
713
714
|
cb_ranges: dict[str, list] = {}
|
|
715
|
+
cb_style_cmaps: dict[str, object] = {}
|
|
714
716
|
|
|
715
717
|
for ax, ly in self._render_queue:
|
|
716
718
|
cb_name = ly.get("colorbar", "axc")
|
|
@@ -724,6 +726,9 @@ class Figure:
|
|
|
724
726
|
if not layer_uses_color(style, coor, method_key):
|
|
725
727
|
continue
|
|
726
728
|
|
|
729
|
+
if style.get("cmap") is not None:
|
|
730
|
+
cb_style_cmaps.setdefault(cb_name, style.get("cmap"))
|
|
731
|
+
|
|
727
732
|
# Load data (pipeline cache hit on second call in render loop)
|
|
728
733
|
runtime_load_layer_runtime_data(self, ly)
|
|
729
734
|
df = ly.get("data")
|
|
@@ -742,6 +747,8 @@ class Figure:
|
|
|
742
747
|
if axc_obj is None or not hasattr(axc_obj, "_cb"):
|
|
743
748
|
continue
|
|
744
749
|
color_cfg = axc_color_config(self.frame, cb_name)
|
|
750
|
+
if color_cfg.get("cmap") is None and cb_name in cb_style_cmaps:
|
|
751
|
+
color_cfg["cmap"] = cb_style_cmaps[cb_name]
|
|
745
752
|
axc_obj._cb.update(
|
|
746
753
|
precompute_colorbar_cb(color_cfg, ranges, logger=self.logger)
|
|
747
754
|
)
|
|
@@ -969,16 +976,8 @@ class Figure:
|
|
|
969
976
|
def plot(self):
|
|
970
977
|
self.render()
|
|
971
978
|
# for layer in self.layers:
|
|
972
|
-
if self.debug:
|
|
973
|
-
|
|
974
|
-
# Demo of Scatter Clip
|
|
975
|
-
x = np.linspace(-1, 2, 121)
|
|
976
|
-
y = np.linspace(-1, 2, 121)
|
|
977
|
-
X, Y = np.meshgrid(x, y)
|
|
978
|
-
self.axtri.scatter(x=X.ravel() + 0.5 * Y.ravel(), y=Y.ravel(), marker='.', s=1, facecolor="#0277BA", edgecolor="None")
|
|
979
|
-
|
|
980
|
-
# Demo of Plot Clip
|
|
981
|
-
self.axtri.plot(x=[-1, 0.5, 0.5, 2], y=[-1.1, 0.6, 0.3, 1.8], linestyle="-", color="#0277BA")
|
|
979
|
+
if self.debug:
|
|
980
|
+
draw_design_reference(self)
|
|
982
981
|
self.savefig()
|
|
983
982
|
import matplotlib.pyplot as plt
|
|
984
983
|
plt.close(self.fig)
|
|
@@ -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)}'.")
|
|
@@ -124,6 +124,23 @@
|
|
|
124
124
|
"va":"center"
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
+
},
|
|
128
|
+
"axlogo": {
|
|
129
|
+
"file": "&JP/jarvisplot/cards/icons/gambit_small.png",
|
|
130
|
+
"text": [
|
|
131
|
+
{
|
|
132
|
+
"x": 0.0,
|
|
133
|
+
"y": -0.5,
|
|
134
|
+
"s": "GAMBIT 2.7",
|
|
135
|
+
"ha": "left",
|
|
136
|
+
"va": "bottom",
|
|
137
|
+
"color": "black",
|
|
138
|
+
"fontfamily": "Fira code",
|
|
139
|
+
"fontsize": 5.0,
|
|
140
|
+
"fontstyle": "normal",
|
|
141
|
+
"fontweight": "normal"
|
|
142
|
+
}
|
|
143
|
+
]
|
|
127
144
|
}
|
|
128
145
|
},
|
|
129
146
|
"Style": {
|
|
@@ -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
|