jarvisplot 1.0.1__py3-none-any.whl

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 (42) hide show
  1. jarvisplot/Figure/adapters.py +773 -0
  2. jarvisplot/Figure/cards/std_axes_adapter_config.json +23 -0
  3. jarvisplot/Figure/data_pipelines.py +87 -0
  4. jarvisplot/Figure/figure.py +1573 -0
  5. jarvisplot/Figure/helper.py +217 -0
  6. jarvisplot/Figure/load_data.py +252 -0
  7. jarvisplot/__init__.py +0 -0
  8. jarvisplot/cards/a4paper/1x1/ternary.json +6 -0
  9. jarvisplot/cards/a4paper/2x1/rect.json +106 -0
  10. jarvisplot/cards/a4paper/2x1/rect5x1.json +344 -0
  11. jarvisplot/cards/a4paper/2x1/rect_cmap.json +181 -0
  12. jarvisplot/cards/a4paper/2x1/ternary.json +139 -0
  13. jarvisplot/cards/a4paper/2x1/ternary_cmap.json +189 -0
  14. jarvisplot/cards/a4paper/4x1/rect.json +106 -0
  15. jarvisplot/cards/a4paper/4x1/rect_cmap.json +174 -0
  16. jarvisplot/cards/a4paper/4x1/ternary.json +139 -0
  17. jarvisplot/cards/a4paper/4x1/ternary_cmap.json +189 -0
  18. jarvisplot/cards/args.json +50 -0
  19. jarvisplot/cards/colors/colormaps.json +140 -0
  20. jarvisplot/cards/default/output.json +11 -0
  21. jarvisplot/cards/gambit/1x1/ternary.json +6 -0
  22. jarvisplot/cards/gambit/2x1/rect_cmap.json +200 -0
  23. jarvisplot/cards/gambit/2x1/ternary.json +139 -0
  24. jarvisplot/cards/gambit/2x1/ternary_cmap.json +205 -0
  25. jarvisplot/cards/icons/JarvisHEP.png +0 -0
  26. jarvisplot/cards/icons/gambit.png +0 -0
  27. jarvisplot/cards/icons/gambit_small.png +0 -0
  28. jarvisplot/cards/style_preference.json +23 -0
  29. jarvisplot/cli.py +64 -0
  30. jarvisplot/client.py +6 -0
  31. jarvisplot/config.py +69 -0
  32. jarvisplot/core.py +237 -0
  33. jarvisplot/data_loader.py +441 -0
  34. jarvisplot/inner_func.py +162 -0
  35. jarvisplot/utils/__init__.py +0 -0
  36. jarvisplot/utils/cmaps.py +258 -0
  37. jarvisplot/utils/interpolator.py +377 -0
  38. jarvisplot-1.0.1.dist-info/METADATA +80 -0
  39. jarvisplot-1.0.1.dist-info/RECORD +42 -0
  40. jarvisplot-1.0.1.dist-info/WHEEL +5 -0
  41. jarvisplot-1.0.1.dist-info/entry_points.txt +2 -0
  42. jarvisplot-1.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import sys
4
+ from sympy.core import numbers as SCNum
5
+ import sympy
6
+ import numpy as np
7
+
8
+ _AllSCNum = (
9
+ SCNum.Float,
10
+ SCNum.Number,
11
+ SCNum.Rational,
12
+ SCNum.Integer,
13
+ SCNum.Infinity,
14
+ SCNum.AlgebraicNumber,
15
+ SCNum.RealNumber,
16
+ SCNum.Zero,
17
+ SCNum.One,
18
+ SCNum.NegativeOne,
19
+ SCNum.NegativeInfinity,
20
+ SCNum.Exp1,
21
+ SCNum.Pi,
22
+ float,
23
+ int,
24
+ np.float16,
25
+ np.float32,
26
+ np.float64,
27
+ np.int8,
28
+ np.int16,
29
+ np.int32,
30
+ np.int64
31
+ )
32
+ _Inner_FCs = {
33
+ # Natural Logarithm
34
+ "log": sympy.log,
35
+ "exp": sympy.exp,
36
+ "ln": sympy.ln,
37
+ # Triangle Function
38
+ "sin": sympy.sin,
39
+ "cos": sympy.cos,
40
+ "tan": sympy.tan,
41
+ "sec": sympy.sec,
42
+ "csc": sympy.csc,
43
+ "cot": sympy.cot,
44
+ "sinc": sympy.sinc,
45
+ "asin": sympy.asin,
46
+ "acos": sympy.acos,
47
+ "atan": sympy.atan,
48
+ "asec": sympy.asec,
49
+ "acsc": sympy.acsc,
50
+ "acot": sympy.acot,
51
+ "atan2":sympy.atan2,
52
+ # Hyperbolic Function
53
+ "sinh": sympy.sinh,
54
+ "cosh": sympy.cosh,
55
+ "tanh": sympy.tanh,
56
+ "sech": sympy.sech,
57
+ "csch": sympy.csch,
58
+ "coth": sympy.coth,
59
+ "asinh": sympy.asinh,
60
+ "acosh": sympy.acosh,
61
+ "atanh": sympy.atanh,
62
+ "acoth": sympy.acoth,
63
+ "asech": sympy.asech,
64
+ "acsch": sympy.acsch,
65
+ # General Math
66
+ "sqrt": sympy.sqrt,
67
+ "Min": sympy.Min,
68
+ "Max": sympy.Max,
69
+ "root": sympy.root,
70
+ "Abs": sympy.Abs,
71
+ }
72
+
73
+ _Constant = {
74
+ "Pi": sympy.pi,
75
+ "E": sympy.E,
76
+ "Inf": np.inf
77
+ }
78
+
79
+ # External function hooks (e.g. user-defined / lazy-loaded interpolators)
80
+ # These are injected into the expression runtime via `update_funcs`.
81
+ #
82
+ # Usage:
83
+ # - core/context code can call `set_external_funcs({...})` once after building ctx.
84
+ # - or provide a getter with `set_external_funcs_getter(lambda: {...})` if the set may change.
85
+ # - values must be callables (LazyCallable is OK).
86
+ _EXTERNAL_FCS = {}
87
+ _EXTERNAL_FCS_GETTER = None
88
+
89
+
90
+ def set_external_funcs(funcs: dict) -> None:
91
+ """Register external functions to be injected by `update_funcs`.
92
+
93
+ Parameters
94
+ ----------
95
+ funcs:
96
+ Mapping of name -> callable (LazyCallable is acceptable).
97
+ """
98
+ global _EXTERNAL_FCS
99
+ _EXTERNAL_FCS = dict(funcs) if funcs is not None else {}
100
+
101
+
102
+ def set_external_funcs_getter(getter) -> None:
103
+ """Register a callable that returns a dict of external functions.
104
+
105
+ The getter should return a `dict[str, callable]`.
106
+ """
107
+ global _EXTERNAL_FCS_GETTER
108
+ _EXTERNAL_FCS_GETTER = getter
109
+
110
+
111
+ def clear_external_funcs() -> None:
112
+ """Clear any registered external functions/getter."""
113
+ global _EXTERNAL_FCS, _EXTERNAL_FCS_GETTER
114
+ _EXTERNAL_FCS = {}
115
+ _EXTERNAL_FCS_GETTER = None
116
+
117
+ def Gauss(xx, mean, err):
118
+ prob = sympy.exp(-0.5 * ((xx - mean) / err)**2)
119
+ return prob
120
+
121
+
122
+ # def Gauss(xx, mean, err):
123
+ # from math import sqrt, pi, exp
124
+ # # prob = 1./ (err * sqrt(2 * pi)) * exp(-0.5*((xx - mean)/err)**2)
125
+ # prob = exp(-0.5*((xx - mean)/err)**2)
126
+ # return prob
127
+
128
+ def Normal(xx, mean, err):
129
+ # from math import sqrt, pi, exp
130
+ prob = 1./ (err * sympy.sqrt(2 * sympy.pi)) * sympy.exp(-0.5*((xx - mean)/err)**2)
131
+ return prob
132
+
133
+ def LogGauss(xx, mean, err):
134
+ prob = -0.5*((xx - mean)/err)**2
135
+ return prob
136
+
137
+ def update_funcs(funcs):
138
+ funcs['sympy'] = sympy
139
+ funcs['Gauss'] = Gauss
140
+ funcs['LogGauss'] = LogGauss
141
+ funcs['Normal'] = Normal
142
+ funcs['Heaviside'] = sympy.Heaviside
143
+
144
+ # Built-in functions
145
+ funcs.update(_Inner_FCs)
146
+
147
+ # External functions (e.g. lazy-loaded interpolators). External takes priority.
148
+ try:
149
+ if _EXTERNAL_FCS_GETTER is not None:
150
+ ext = _EXTERNAL_FCS_GETTER() or {}
151
+ funcs.update(ext)
152
+ elif _EXTERNAL_FCS:
153
+ funcs.update(_EXTERNAL_FCS)
154
+ except Exception:
155
+ # Never fail expression evaluation because external injection failed.
156
+ pass
157
+
158
+ return funcs
159
+
160
+ def update_const(vars):
161
+ vars.update(_Constant)
162
+ return vars
File without changes
@@ -0,0 +1,258 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ JarvisPLOT colormap loader: JSON-only, no built-ins.
5
+ Public API:
6
+ - setup(json_path: str|None = None, force: bool = True) -> dict
7
+ - register_from_json(json_path: str|os.PathLike, force: bool = True) -> dict
8
+ - list_available() -> list[str]
9
+ """
10
+ from __future__ import annotations
11
+ from typing import Any, Dict, List, Tuple, Optional, Union
12
+ from pathlib import Path
13
+ import json
14
+ import os
15
+
16
+ import matplotlib as mpl
17
+ import matplotlib.cm as mcm
18
+ from matplotlib.colors import ListedColormap, LinearSegmentedColormap, Colormap
19
+
20
+ import inspect
21
+ import matplotlib.pyplot as plt
22
+ def _register(name: str, cmap: Colormap, force: bool) -> bool:
23
+ """
24
+ Try multiple registration paths depending on Matplotlib version:
25
+ 1) mpl.colormaps.register(...), passing 'name' and 'override' only if supported
26
+ 2) plt.register_cmap(name=..., cmap=...)
27
+ 3) update legacy cm.cmap_d if present
28
+ Returns True on success.
29
+ """
30
+ name = str(name)
31
+ # Path 1: modern registry
32
+ try:
33
+ reg = getattr(mpl.colormaps, "register", None)
34
+ if reg is not None:
35
+ kwargs = {}
36
+ try:
37
+ sig = inspect.signature(reg)
38
+ if "name" in sig.parameters:
39
+ kwargs["name"] = name
40
+ else:
41
+ try:
42
+ cmap.name = name
43
+ except Exception:
44
+ pass
45
+ if "override" in sig.parameters:
46
+ kwargs["override"] = bool(force)
47
+ except Exception:
48
+ # If signature introspection fails, try safest call (cmap only).
49
+ try:
50
+ cmap.name = name
51
+ except Exception:
52
+ pass
53
+ # Call register
54
+ if kwargs:
55
+ reg(cmap, **kwargs)
56
+ else:
57
+ reg(cmap)
58
+ ok = True
59
+ else:
60
+ ok = False
61
+ except Exception:
62
+ ok = False
63
+ # Path 2: pyplot register_cmap
64
+ if not ok:
65
+ try:
66
+ plt.register_cmap(name=name, cmap=cmap)
67
+ ok = True
68
+ except Exception:
69
+ ok = False
70
+ # Path 3: legacy dict
71
+ if not ok and hasattr(mcm, "cmap_d"):
72
+ try:
73
+ mcm.cmap_d[name] = cmap
74
+ ok = True
75
+ except Exception:
76
+ ok = False
77
+ # final visibility sanity check
78
+ try:
79
+ visible = (name in list(mpl.colormaps)) or (hasattr(mcm, "cmap_d") and name in mcm.cmap_d)
80
+ except Exception:
81
+ visible = False
82
+ return bool(ok and visible)
83
+
84
+ class CmapSpecError(Exception):
85
+ pass
86
+
87
+ def _norm_color_list(seq):
88
+ """
89
+ Normalize a list of colors into formats Matplotlib accepts.
90
+ Accepts:
91
+ - hex strings "#RRGGBB" / "#RRGGBBAA"
92
+ - RGB/RGBA tuples or lists in 0..1 or 0..255
93
+ Returns a new list; raises CmapSpecError on invalid entries.
94
+ """
95
+ out = []
96
+ for c in list(seq):
97
+ if isinstance(c, str):
98
+ out.append(c)
99
+ continue
100
+ if isinstance(c, (list, tuple)):
101
+ vals = list(c)
102
+ if not vals:
103
+ raise CmapSpecError("empty color tuple")
104
+ if all(isinstance(v, (int, float)) for v in vals):
105
+ # If any component >1, assume 0..255 and scale
106
+ mx = max(abs(float(v)) for v in vals)
107
+ if mx > 1.0:
108
+ vals = [float(v)/255.0 for v in vals]
109
+ out.append(tuple(vals))
110
+ continue
111
+ raise CmapSpecError(f"unsupported color value: {c!r}")
112
+ return out
113
+
114
+ JsonLike = Union[Dict[str, Any], List[Dict[str, Any]]]
115
+
116
+ def _read_json(path: Path) -> Optional[JsonLike]:
117
+ try:
118
+ with path.open("r", encoding="utf-8") as f:
119
+ return json.load(f)
120
+ except Exception:
121
+ return None
122
+
123
+ def _to_linear(name: str, colors: List[Any]) -> Optional[LinearSegmentedColormap]:
124
+ try:
125
+ # Accept either ["#hex", ...] or [[pos, color], ...]
126
+ if colors and isinstance(colors[0], (list, tuple)) and len(colors[0]) == 2 and not isinstance(colors[0][1], (list, tuple)) and not (isinstance(colors[0][1], str) and colors[0][1].startswith("#") or isinstance(colors[0][1], str)):
127
+ # The above heuristic is too brittle; simplify by explicit shape check below
128
+ pass
129
+ except Exception:
130
+ pass
131
+ try:
132
+ if colors and isinstance(colors[0], (list, tuple)) and len(colors[0]) == 2:
133
+ pts: List[Tuple[float, Any]] = []
134
+ for p, c in colors:
135
+ try:
136
+ pp = float(p)
137
+ except Exception:
138
+ continue
139
+ pp = max(0.0, min(1.0, pp))
140
+ pts.append((pp, c))
141
+ pts.sort(key=lambda t: t[0])
142
+ # Normalize color payload
143
+ pts = [(p, _norm_color_list([c])[0]) for (p, c) in pts]
144
+ return LinearSegmentedColormap.from_list(name, pts)
145
+ # Plain list of colors
146
+ return LinearSegmentedColormap.from_list(name, _norm_color_list(list(colors)))
147
+ except Exception:
148
+ return None
149
+
150
+ def _to_listed(name: str, colors: List[Any]) -> Optional[ListedColormap]:
151
+ try:
152
+ return ListedColormap(_norm_color_list(list(colors)), name=name)
153
+ except Exception:
154
+ return None
155
+
156
+ def _build(spec: Dict[str, Any]) -> Colormap:
157
+ name = spec.get("name")
158
+ colors = spec.get("colors")
159
+ if not isinstance(name, str) or not name.strip():
160
+ raise CmapSpecError("missing or invalid 'name'")
161
+ if not isinstance(colors, (list, tuple)) or not colors:
162
+ raise CmapSpecError(f"cmap {name!r}: missing or invalid 'colors'")
163
+ t = str(spec.get("type", "listed")).lower()
164
+ if t in ("linear", "segmented", "continuous"):
165
+ cm = _to_linear(name, list(colors))
166
+ else:
167
+ cm = _to_listed(name, list(colors))
168
+ if cm is None:
169
+ raise CmapSpecError(f"failed to build colormap {name!r}")
170
+ return cm
171
+
172
+ def register_from_json(json_path: Union[str, os.PathLike], force: bool = True) -> Dict[str, Any]:
173
+ p = Path(json_path).expanduser().resolve()
174
+ reg: List[str] = []
175
+ fail: List[str] = []
176
+ err: Dict[str, str] = {}
177
+ try:
178
+ with p.open("r", encoding="utf-8") as f:
179
+ data = json.load(f)
180
+ except Exception as e:
181
+ return {"registered": [], "failed": [], "errors": {str(p): f"read json failed: {e}"}, "path": str(p)}
182
+ # Accept three shapes: dict with 'colormaps', single dict, or top-level list
183
+ specs: List[Dict[str, Any]] = []
184
+ aliases: Dict[str, str] = {}
185
+ if isinstance(data, dict):
186
+ if "name" in data and "colors" in data:
187
+ specs.append(data)
188
+ else:
189
+ cmaps_list = data.get("colormaps")
190
+ if isinstance(cmaps_list, list):
191
+ for item in cmaps_list:
192
+ if isinstance(item, dict):
193
+ specs.append(item)
194
+ aliases = data.get("aliases", {}) or {}
195
+ elif isinstance(data, list):
196
+ for item in data:
197
+ if isinstance(item, dict):
198
+ specs.append(item)
199
+ # Build + register
200
+ for spec in specs:
201
+ try:
202
+ name = spec.get("name")
203
+ colors = spec.get("colors")
204
+ ctype = str(spec.get("type", "listed")).lower()
205
+ if not isinstance(name, str) or not name or not isinstance(colors, (list, tuple)) or not colors:
206
+ raise ValueError("invalid spec (need 'name' and non-empty 'colors')")
207
+ if ctype in ("linear", "segmented", "continuous"):
208
+ cm = _to_linear(name, list(colors))
209
+ else:
210
+ cm = _to_listed(name, list(colors))
211
+ if cm is None:
212
+ raise ValueError("failed to build colormap")
213
+ if _register(name, cm, force=force):
214
+ reg.append(name)
215
+ try:
216
+ cm_r = cm.reversed()
217
+ if _register(f"{name}_r", cm_r, force=force):
218
+ reg.append(f"{name}_r")
219
+ except Exception:
220
+ pass
221
+ else:
222
+ fail.append(name)
223
+ except Exception as e:
224
+ nm = spec.get("name", "<missing-name>")
225
+ fail.append(nm)
226
+ err[nm] = str(e)
227
+ # aliases (if any): only when target is visible
228
+ for alias, target in (aliases.items() if isinstance(aliases, dict) else []):
229
+ try:
230
+ base = mpl.colormaps.get(target)
231
+ except Exception:
232
+ base = None
233
+ if base is None and hasattr(mcm, "cmap_d"):
234
+ base = mcm.cmap_d.get(target)
235
+ if base is None:
236
+ fail.append(alias)
237
+ err[alias] = f"alias target not found: {target}"
238
+ elif _register(alias, base, force=force):
239
+ reg.append(alias)
240
+ else:
241
+ fail.append(alias)
242
+ return {"registered": reg, "failed": fail, "errors": err, "path": str(p)}
243
+
244
+ def setup(json_path: Optional[Union[str, os.PathLike]] = None, force: bool = True) -> Dict[str, Any]:
245
+ src = json_path
246
+ if not src:
247
+ return {"registered": [], "failed": [], "errors": {}, "path": None}
248
+ return register_from_json(src, force=force)
249
+
250
+ def list_available() -> List[str]:
251
+ try:
252
+ return list(mpl.colormaps)
253
+ except Exception:
254
+ if hasattr(mcm, "cmap_d"):
255
+ return sorted(mcm.cmap_d.keys())
256
+ return []
257
+
258
+ __all__ = ["setup", "register_from_json", "list_available"]