jarvisplot 1.0.0__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.

Potentially problematic release.


This version of jarvisplot might be problematic. Click here for more details.

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.0.dist-info/METADATA +93 -0
  39. jarvisplot-1.0.0.dist-info/RECORD +42 -0
  40. jarvisplot-1.0.0.dist-info/WHEEL +5 -0
  41. jarvisplot-1.0.0.dist-info/entry_points.txt +2 -0
  42. jarvisplot-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,217 @@
1
+ # jarvisplot/Figure/helper.py
2
+ #!/usr/bin/env python3
3
+
4
+ from matplotlib.axes import Axes
5
+ import numpy as np
6
+
7
+ def split_fill_kwargs(kw_all):
8
+ edge_kws = {
9
+ "edgecolor", "ec",
10
+ "linewidth", "lw",
11
+ "linestyle", "ls",
12
+ "joinstyle", "capstyle",
13
+ "alpha"
14
+ }
15
+ face_kws = {
16
+ "facecolor", "fc",
17
+ "hatch",
18
+ "hatch_linewidth",
19
+ "alpha", "edgecolor"
20
+ }
21
+ kw_edge = {}
22
+ kw_face = {}
23
+ kw_rest = {}
24
+ for k, v in kw_all.items():
25
+ if k in edge_kws:
26
+ kw_edge[k] = v
27
+ if k in face_kws:
28
+ kw_face[k] = v
29
+ else:
30
+ kw_rest[k] = v
31
+ return kw_edge, kw_face, kw_rest
32
+
33
+ def plot_shapely_boundary(ax, geom, *, transform=None, **plot_kw):
34
+ if geom is None or geom.is_empty:
35
+ return []
36
+
37
+ lines = []
38
+ gt = geom.geom_type
39
+ if gt == "LineString":
40
+ lines = [geom]
41
+ elif gt == "MultiLineString":
42
+ lines = list(geom.geoms)
43
+ elif gt == "GeometryCollection":
44
+ for g in geom.geoms:
45
+ if g.geom_type == "LineString":
46
+ lines.append(g)
47
+ elif g.geom_type == "MultiLineString":
48
+ lines.extend(list(g.geoms))
49
+ else:
50
+ # 兜底:有时 boundary 可能给出别的类型
51
+ try:
52
+ b = geom.boundary
53
+ return _plot_shapely_boundary(ax, b, transform=transform, **plot_kw)
54
+ except Exception:
55
+ return []
56
+
57
+ artists = []
58
+ for ln in lines:
59
+ xs, ys = ln.coords.xy
60
+ artists += ax.plot(list(xs), list(ys), transform=transform, **plot_kw)
61
+ return artists
62
+
63
+
64
+ # helper: convert infinite regions to finite polygons (public-domain recipe)
65
+ # Clip polygon to rectangle extent (Sutherland-Hodgman for convex quad)
66
+ def voronoi_finite_polygons_2d(vor, radius=None):
67
+ if vor.points.shape[1] != 2:
68
+ raise ValueError("Requires 2D input")
69
+ new_regions = []
70
+ new_vertices = vor.vertices.tolist()
71
+ center = vor.points.mean(axis=0)
72
+ if radius is None:
73
+ radius = np.ptp(vor.points).max() * 2
74
+ # Construct a map ridge_vertices -> points (pairs)
75
+ all_ridges = {}
76
+ for (p1, p2), (v1, v2) in zip(vor.ridge_points, vor.ridge_vertices):
77
+ all_ridges.setdefault(p1, []).append((p2, v1, v2))
78
+ all_ridges.setdefault(p2, []).append((p1, v1, v2))
79
+ # Reconstruct infinite regions
80
+ for p1, region_index in enumerate(vor.point_region):
81
+ vertices = vor.regions[region_index]
82
+ if -1 not in vertices:
83
+ new_regions.append(vertices)
84
+ continue
85
+ ridges = all_ridges[p1]
86
+ new_region = [v for v in vertices if v != -1]
87
+ for p2, v1, v2 in ridges:
88
+ if v2 < 0: v1, v2 = v2, v1
89
+ if v1 >= 0 and v2 >= 0:
90
+ continue
91
+ # Compute the missing endpoint at infinity
92
+ t = vor.points[p2] - vor.points[p1]
93
+ t /= np.linalg.norm(t)
94
+ n = np.array([-t[1], t[0]]) # normal
95
+ midpoint = vor.points[[p1, p2]].mean(axis=0)
96
+ direction = np.sign(np.dot(midpoint - center, n)) * n
97
+ far_point = vor.vertices[v2] + direction * radius
98
+ new_region.append(len(new_vertices))
99
+ new_vertices.append(far_point.tolist())
100
+ # Sort region vertices counterclockwise
101
+ vs = np.asarray([new_vertices[v] for v in new_region])
102
+ c = vs.mean(axis=0)
103
+ angles = np.arctan2(vs[:, 1] - c[1], vs[:, 0] - c[0])
104
+ new_region = np.array(new_region)[np.argsort(angles)].tolist()
105
+ new_regions.append(new_region)
106
+ return new_regions, np.asarray(new_vertices)
107
+
108
+
109
+ def _clip_poly_to_rect(poly, rect):
110
+ # rect: (xmin, xmax, ymin, ymax)
111
+ xmin, xmax, ymin, ymax = rect
112
+ def _clip(edges, inside, intersect):
113
+ out = []
114
+ if not edges:
115
+ return out
116
+ S = edges[-1]
117
+ for E in edges:
118
+ if inside(E):
119
+ if inside(S):
120
+ out.append(E)
121
+ else:
122
+ out.append(intersect(S, E))
123
+ out.append(E)
124
+ elif inside(S):
125
+ out.append(intersect(S, E))
126
+ S = E
127
+ return out
128
+ def clip_left(P):
129
+ return _clip(P, lambda p: p[0] >= xmin, lambda s ,e: (xmin, s[1] + (e[1 ] -s[1] ) *(xmin - s[0] ) /(e[0 ] -s[0]) ))
130
+ def clip_right(P):
131
+ return _clip(P, lambda p: p[0] <= xmax, lambda s ,e: (xmax, s[1] + (e[1 ] -s[1] ) *(xmax - s[0] ) /(e[0 ] -s[0]) ))
132
+ def clip_bottom(P):
133
+ return _clip(P, lambda p: p[1] >= ymin, lambda s ,e: (s[0] + (e[0 ] -s[0] ) *(ymin - s[1] ) /(e[1 ] -s[1]), ymin ))
134
+ def clip_top(P):
135
+ return _clip(P, lambda p: p[1] <= ymax, lambda s ,e: (s[0] + (e[0 ] -s[0] ) *(ymax - s[1] ) /(e[1 ] -s[1]), ymax ))
136
+ P = poly
137
+ for fn in (clip_left, clip_right, clip_bottom, clip_top):
138
+ P = fn(P)
139
+ if not P:
140
+ break
141
+ return P
142
+
143
+
144
+
145
+ # ---- helpers for masking by extend on filled contours ----
146
+
147
+ def _resolve_vlim(z, vmin=None, vmax=None, levels=None, norm=None):
148
+ """Derive effective vmin/vmax from norm/levels/fallback to data."""
149
+ import numpy as np
150
+ if norm is not None:
151
+ vmin = getattr(norm, "vmin", vmin)
152
+ vmax = getattr(norm, "vmax", vmax)
153
+ if levels is not None and np.ndim(levels) > 0:
154
+ vmin = levels[0] if vmin is None else vmin
155
+ vmax = levels[-1] if vmax is None else vmax
156
+ if vmin is None:
157
+ vmin = float(np.nanmin(z))
158
+ if vmax is None:
159
+ vmax = float(np.nanmax(z))
160
+ return float(vmin), float(vmax)
161
+
162
+
163
+ def _mask_by_extend(z, *, extend="neither", vmin=None, vmax=None, levels=None, norm=None):
164
+ """
165
+ Return masked z according to extend semantics:
166
+ - 'min' : mask z < vmin
167
+ - 'max' : mask z > vmax
168
+ - 'both' : mask outside [vmin, vmax]
169
+ - 'neither': no masking
170
+ Also returns effective (vmin, vmax).
171
+ """
172
+ import numpy as np
173
+ z = np.asarray(z)
174
+ e = (extend or "neither").lower()
175
+ if e not in ("neither", "min", "max", "both"):
176
+ e = "neither"
177
+ vmin_eff, vmax_eff = _resolve_vlim(z, vmin=vmin, vmax=vmax, levels=levels, norm=norm)
178
+ mask = np.zeros_like(z, dtype=bool)
179
+ if e in ("min", "both"):
180
+ mask |= (z < vmin_eff)
181
+ if e in ("max", "both"):
182
+ mask |= (z > vmax_eff)
183
+ return np.ma.masked_array(z, mask=mask), vmin_eff, vmax_eff
184
+
185
+ # —— 小工具:对 artist 或容器做统一 clip_path 应用 ——
186
+ def _auto_clip(artists, ax: Axes, clip_path):
187
+ if clip_path is None:
188
+ return artists
189
+ def _apply_one(a):
190
+ try:
191
+ a.set_clip_path(clip_path, transform=ax.transData)
192
+ except Exception:
193
+ pass
194
+ # always apply to the container itself first
195
+ _apply_one(artists)
196
+ try:
197
+ iter(artists)
198
+ except TypeError:
199
+ return artists
200
+ else:
201
+ for item in artists:
202
+ # 先试 artist 本体
203
+ _apply_one(item)
204
+ # matplotlib 常见容器:collections / patches / lines
205
+ coll = getattr(item, "collections", None)
206
+ if coll:
207
+ for c in coll:
208
+ _apply_one(c)
209
+ patches = getattr(item, "patches", None)
210
+ if patches:
211
+ for p in patches:
212
+ _apply_one(p)
213
+ lines = getattr(item, "lines", None)
214
+ if lines:
215
+ for ln in lines:
216
+ _apply_one(ln)
217
+ return artists
@@ -0,0 +1,252 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import json
6
+ from copy import deepcopy
7
+
8
+ def eval_series(df: pd.DataFrame, set: dict, logger):
9
+ """
10
+ Evaluate an expression/column name against df safely.
11
+ - If expr is a direct column name, returns that series.
12
+ - If expr is a python expression, eval with df columns in scope.
13
+ """
14
+ try:
15
+ logger.debug("Loading variable expression -> {}".format(set['expr']))
16
+ except:
17
+ pass
18
+ if not "expr" in set.keys():
19
+ raise ValueError(f"expr need for axes {set}.")
20
+ if set["expr"] in df.columns:
21
+ arr = df[set["expr"]].values
22
+ if np.isnan(arr).sum() and "fillna" in set.keys():
23
+ arr = np.where(np.isnan(arr), float(set['fillna']), arr)
24
+ else:
25
+ # safe-ish eval with only df columns in locals
26
+ local_vars = df.to_dict("series")
27
+ import math
28
+ from ..inner_func import update_funcs
29
+ allowed_globals = update_funcs({"np": np, "math": math})
30
+ arr = eval(set["expr"], allowed_globals, local_vars)
31
+ if np.isnan(arr).sum() and "fillna" in set.keys():
32
+ arr = np.where(np.isnan(arr), float(set['fillna']), arr)
33
+ return np.asarray(arr)
34
+
35
+
36
+ def profiling(df, prof, logger):
37
+ def profile_bridson_sorted(idx, xx, yy, zz, radius, msk):
38
+ for i in range(len(idx)):
39
+ if not msk[i]:
40
+ continue
41
+ dx = xx[idx > idx[i]] - xx[i]
42
+ dy = yy[idx > idx[i]] - yy[i]
43
+ dz = zz[idx > idx[i]] - zz[i]
44
+ dist0 = (dx**2 + dy**2)**0.5
45
+ dist1 = (dx**2 + dy**2 + dz**2)**0.5
46
+ near0 = (dist0 < 0.707 * radius) | (dist0 < radius) & (dist1 > radius)
47
+ sel = (idx > idx[i])
48
+ msk[sel] &= ~near0
49
+ return msk
50
+
51
+ bin = prof.get("bin", 100)
52
+ coors = prof.get("coordinates", {})
53
+ obj = prof.get("objective", "max")
54
+ grid = prof.get("grid_points", "rect")
55
+ gdata = None
56
+
57
+ radius = 1 / bin
58
+ if "expr" in coors['x'].keys():
59
+ x = eval_series(df, coors['x'], logger)
60
+ else:
61
+ x = df['x']
62
+
63
+ if "expr" in coors['y'].keys():
64
+ y = eval_series(df, coors['y'], logger)
65
+ else:
66
+ y = df['y']
67
+
68
+ if "expr" in coors['z'].keys():
69
+ z = eval_series(df, coors['z'], logger)
70
+ else:
71
+ z = df['z']
72
+
73
+ logger.debug("After loading profiling x, y, z. ")
74
+
75
+ if grid == "ternary":
76
+ xlim = coors['x'].get("lim", [0, 1])
77
+ ylim = coors['y'].get("lim", [0, 1])
78
+ zlim = coors['z'].get("lim", [np.min(z), np.max(z)])
79
+ xscale = coors['x'].get("scale", "linear")
80
+ yscale = coors['y'].get("scale", "linear")
81
+ zscale = coors['z'].get("scale", "linear")
82
+ zind = coors['z'].get("name", "z")
83
+ xind = coors['x'].get("name", "x")
84
+ yind = coors['y'].get("name", "y")
85
+ elif grid == "rect":
86
+ xlim = coors['x'].get("lim", [np.min(x), np.max(x)])
87
+ ylim = coors['y'].get("lim", [np.min(y), np.max(y)])
88
+ zlim = coors['z'].get("lim", [np.min(z), np.max(z)])
89
+
90
+ xscale = coors['x'].get("scale", "linear")
91
+ yscale = coors['y'].get("scale", "linear")
92
+ zscale = coors['z'].get("scale", "linear")
93
+
94
+ zind = coors['z'].get("name", "z")
95
+ xind = coors['x'].get("name", "x")
96
+ yind = coors['y'].get("name", "y")
97
+
98
+
99
+ # profiling will add new columns into dataframe, so that can be used in the next step
100
+ df[xind] = x
101
+ df[yind] = y
102
+ df[zind] = z
103
+ # print(x.min(), x.max(), y.min(), y.max(), z.min(), z.max())
104
+
105
+
106
+ if grid == "ternary":
107
+ bb = np.linspace(0, 1, bin + 1)
108
+ rr = np.linspace(0, 1, bin + 1)
109
+ Bg, Rg = np.meshgrid(bb, rr)
110
+ r = Rg.ravel()
111
+ b = Bg.ravel()
112
+ l = 1.0 - b - r
113
+ mask = (l >= 0) & (b >= 0) & (r >= 0)
114
+ x = b + 0.5 * r
115
+ y = r
116
+ xxg, yyg = x[mask], y[mask]
117
+ llg, bbg, rrg, = l[mask], b[mask], r[mask]
118
+ gdata = pd.DataFrame({
119
+ xind: xxg,
120
+ yind: yyg,
121
+ zind: np.ones(xxg.shape) * (np.min(z) - 0.1)
122
+ })
123
+
124
+ elif grid == "rect":
125
+ xx = np.linspace(xlim[0], xlim[1], bin+1)
126
+ yy = np.linspace(ylim[0], ylim[1], bin+1)
127
+ xg, yg = np.meshgrid(xx, yy)
128
+
129
+ gdata = pd.DataFrame({
130
+ xind: xg.ravel(),
131
+ yind: yg.ravel(),
132
+ zind: np.ones(xg.ravel().shape) * (np.min(z) - 0.1)
133
+ })
134
+
135
+ if obj == "max":
136
+ df = df.sort_values(zind, ascending=False).reset_index(drop=True)
137
+ elif obj == "min":
138
+ df = df.sort_values(zind, ascending=True).reset_index(drop=True)
139
+ else:
140
+ df = df.sort_values(zind, ascending=False).reset_index(drop=True)
141
+ logger.error("Sort dataset method: objective: {} not support, using default value -> 'max'".format(obj))
142
+ df = pd.concat([df, gdata], ignore_index=True)
143
+
144
+ idx = deepcopy(np.array(df.index))
145
+ xx = deepcopy(np.array(df[xind]))
146
+ yy = deepcopy(np.array(df[yind]))
147
+ zz = deepcopy(np.array(df[zind]))
148
+ # mapping xx, yy, zz to range [0, 1]
149
+ if xscale == "log":
150
+ xx = (np.log(xx) - np.log(xlim[0])) / (np.log(xlim[1]) - np.log(xlim[0]))
151
+ else: # linear scale
152
+ xx = (xx - xlim[0]) / (xlim[1] - xlim[0])
153
+
154
+ if yscale == "log":
155
+ yy = (np.log(yy) - np.log(ylim[0])) / (np.log(ylim[1]) - np.log(ylim[0]))
156
+ else: # linear scale
157
+ yy = (yy - ylim[0]) / (ylim[1] - ylim[0])
158
+
159
+ if zscale == "log":
160
+ zz = (np.log(zz) - np.log(zlim[0])) / (np.log(zlim[1]) - np.log(zlim[0]))
161
+ else: # linear scale
162
+ zz = (zz - zlim[0]) / (zlim[1] - zlim[0])
163
+
164
+ # (removed print(radius))
165
+ msk = np.full(idx.shape, True)
166
+ msk = profile_bridson_sorted(idx, xx, yy, zz, radius, msk)
167
+ df = df.iloc[idx[msk]]
168
+
169
+ return df
170
+
171
+
172
+
173
+
174
+
175
+
176
+ def filter(df, condition, logger):
177
+ try:
178
+ if isinstance(condition, bool):
179
+ return df.copy() if condition else df.iloc[0:0].copy()
180
+ if isinstance(condition, (int, float)) and condition in (0, 1):
181
+ return df.copy() if int(condition) == 1 else df.iloc[0:0].copy()
182
+
183
+ if isinstance(condition, str):
184
+ s = condition.strip()
185
+ low = s.lower()
186
+ if low in {"true", "t", "yes", "y"}:
187
+ return df.copy()
188
+ if low in {"false", "f", "no", "n"}:
189
+ return df.iloc[0:0].copy()
190
+ s = s.replace("&&", " & ").replace("||", " | ")
191
+ condition = s
192
+ else:
193
+ raise TypeError(f"Unsupported condition type: {type(condition)}")
194
+
195
+ from ..inner_func import update_funcs
196
+ import math
197
+ allowed_globals = update_funcs({"np": np, "math": math})
198
+ local_vars = df.to_dict("series")
199
+ mask = eval(condition, allowed_globals, local_vars)
200
+
201
+ if isinstance(mask, (bool, np.bool_, int, float)):
202
+ return df.copy() if bool(mask) else df.iloc[0:0].copy()
203
+ if not isinstance(mask, pd.Series):
204
+ mask = pd.Series(mask, index=df.index)
205
+ mask = mask.astype(bool)
206
+ return df[mask].copy()
207
+ except Exception as e:
208
+ logger.error(f"Errors when evaluating condition -> {condition}:\n\t{e}")
209
+ return pd.DataFrame(index=df.index).iloc[0:0].copy()
210
+
211
+ def addcolumn(df, adds, logger):
212
+ try:
213
+ name = adds.get("name", False)
214
+ expr = adds.get("expr", False)
215
+ if not (name and expr):
216
+ logger.error("Error in loading add_column -> {}".format(adds))
217
+ from ..inner_func import update_funcs
218
+ import math
219
+ allowed_globals = update_funcs({"np": np, "math": math})
220
+ local_vars = df.to_dict("series")
221
+ value = eval(str(expr), allowed_globals, local_vars)
222
+ df[name] = value
223
+ return df
224
+ except Exception as e:
225
+ logger.error("Errors when add new column -> {}:\n\t{}".format(adds, json.dumps(e)))
226
+ return df
227
+
228
+ def sortby(df, expr, logger):
229
+ try:
230
+ return sort_df_by_expr(df, expr)
231
+ except Exception as e:
232
+ logger.warning(f"sortby failed for expr={expr}: {e}")
233
+ return df
234
+
235
+ def sort_df_by_expr(self, df: pd.DataFrame, expr: str, logger) -> pd.DataFrame:
236
+ """
237
+ Sort the dataframe by evaluating the given expression.
238
+ The expression can be a column name or a valid expression understood by _eval_series.
239
+ Returns a new DataFrame sorted ascending by the evaluated values.
240
+ """
241
+ if df is None or expr is None:
242
+ return df
243
+ try:
244
+ # Try evaluate as expression (could be column or expression)
245
+ values = eval_series(df, {"expr": expr}, logger)
246
+ df = df.assign(__sortkey__=values)
247
+ df = df.sort_values(by="__sortkey__", ascending=True)
248
+ df = df.drop(columns=["__sortkey__"])
249
+ return df
250
+ except Exception as e:
251
+ logger.warning(f"LB: sortby failed for expr={expr}: {e}")
252
+ return df
jarvisplot/__init__.py ADDED
File without changes
@@ -0,0 +1,6 @@
1
+ {
2
+ "Figure": {
3
+ "figsize": []
4
+ },
5
+ "Style": {}
6
+ }
@@ -0,0 +1,106 @@
1
+ {
2
+ "Frame": {
3
+ "figure": {
4
+ "figsize": [
5
+ 3.3,
6
+ 2.75
7
+ ]
8
+ },
9
+ "axes": {
10
+ "axlogo": {
11
+ "rect": [
12
+ 0.01,
13
+ 0.01,
14
+ 0.06,
15
+ 0.072
16
+ ],
17
+ "frameon": false,
18
+ "yticks": [],
19
+ "xticks": [],
20
+ "xlim": [
21
+ 0,
22
+ 1024
23
+ ],
24
+ "ylim": [
25
+ 1024,
26
+ 0
27
+ ]
28
+ },
29
+ "ax": {
30
+ "rect": [
31
+ 0.140,
32
+ 0.168,
33
+ 0.840,
34
+ 0.775
35
+ ]
36
+ }
37
+ },
38
+ "ax": {
39
+ "frame": {
40
+ "linewidth": 0.8,
41
+ "solid_capstyle": "projecting",
42
+ "solid_joinstyle": "miter",
43
+ "clip_on": false,
44
+ "c": "#21171A",
45
+ "zorder": 100
46
+ },
47
+ "grid": {
48
+ "sep": 0.1,
49
+ "style": {
50
+ "color": "#C2C2C269",
51
+ "linewidth": 0.3,
52
+ "linestyle": "--"
53
+ }
54
+ },
55
+ "ticks": {
56
+ "both": {
57
+ "labelsize": 6,
58
+ "direction": "in",
59
+ "labelfontfamily": "sans",
60
+ "top": true,
61
+ "left": true,
62
+ "right": true,
63
+ "bottom": true,
64
+ "which": "both"
65
+ },
66
+ "major": {
67
+ "which": "major",
68
+ "length": 4,
69
+ "width": 0.4,
70
+ "color": "black"
71
+ },
72
+ "minor": {
73
+ "which": "minor",
74
+ "length": 2,
75
+ "width": 0.4,
76
+ "color": "black"
77
+ }
78
+ },
79
+ "labels": {
80
+ "xlabel": {
81
+ "loc": "right",
82
+ "fontsize": 12,
83
+ "fontfamily": "STIXGeneral"
84
+ },
85
+ "ylabel": {
86
+ "loc": "top",
87
+ "fontsize": 12,
88
+ "fontfamily": "STIXGeneral"
89
+ }
90
+ }
91
+ },
92
+ "axlogo": {
93
+ "file": "&JP/jarvisplot/cards/icons/JarvisHEP.png"
94
+ }
95
+ },
96
+ "Style": {
97
+ "scatter": {
98
+ "s": 1.0,
99
+ "marker": "^",
100
+ "linewidths": 0.02,
101
+ "alpha": 1.0,
102
+ "zorder": 30,
103
+ "edgecolor": "white"
104
+ }
105
+ }
106
+ }