scitex 2.3.0__py3-none-any.whl → 2.4.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.
- scitex/ai/classification/reporters/reporter_utils/_Plotter.py +1 -1
- scitex/ai/plt/__init__.py +2 -2
- scitex/ai/plt/{_plot_conf_mat.py → _stx_conf_mat.py} +3 -3
- scitex/config/PriorityConfig.py +195 -0
- scitex/config/__init__.py +24 -0
- scitex/io/_save.py +125 -34
- scitex/io/_save_modules/_image.py +37 -20
- scitex/plt/__init__.py +470 -17
- scitex/plt/_subplots/_AxisWrapper.py +98 -50
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +254 -124
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +49 -8
- scitex/plt/_subplots/_SubplotsWrapper.py +76 -91
- scitex/plt/_subplots/_export_as_csv.py +127 -58
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +25 -16
- scitex/plt/_subplots/_export_as_csv_formatters/_format_contourf.py +54 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_hexbin.py +41 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_hist2d.py +41 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow.py +59 -47
- scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +42 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_pie.py +42 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot.py +72 -35
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_box.py +1 -1
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +53 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stem.py +42 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_step.py +42 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +48 -0
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_conf_mat.py → _format_stx_conf_mat.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_ecdf.py → _format_stx_ecdf.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_fillv.py → _format_stx_fillv.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_heatmap.py → _format_stx_heatmap.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_image.py → _format_stx_image.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_joyplot.py → _format_stx_joyplot.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_line.py → _format_stx_line.py} +3 -3
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_mean_ci.py → _format_stx_mean_ci.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_mean_std.py → _format_stx_mean_std.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_median_iqr.py → _format_stx_median_iqr.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_raster.py → _format_stx_raster.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_rectangle.py → _format_stx_rectangle.py} +1 -1
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_scatter_hist.py → _format_stx_scatter_hist.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_shaded_line.py → _format_stx_shaded_line.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_violin.py → _format_stx_violin.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/verify_formatters.py +23 -23
- scitex/plt/ax/__init__.py +16 -15
- scitex/plt/ax/_plot/__init__.py +30 -30
- scitex/plt/ax/_plot/_add_fitted_line.py +65 -11
- scitex/plt/ax/_plot/_plot_statistical_shaded_line.py +104 -76
- scitex/plt/ax/_plot/{_plot_conf_mat.py → _stx_conf_mat.py} +10 -10
- scitex/plt/ax/_plot/_stx_ecdf.py +109 -0
- scitex/plt/ax/_plot/{_plot_fillv.py → _stx_fillv.py} +7 -7
- scitex/plt/ax/_plot/_stx_heatmap.py +366 -0
- scitex/plt/ax/_plot/{_plot_image.py → _stx_image.py} +1 -1
- scitex/plt/ax/_plot/_stx_joyplot.py +113 -0
- scitex/plt/ax/_plot/{_plot_raster.py → _stx_raster.py} +37 -25
- scitex/plt/ax/_plot/{_plot_rectangle.py → _stx_rectangle.py} +10 -9
- scitex/plt/ax/_plot/{_plot_scatter_hist.py → _stx_scatter_hist.py} +1 -1
- scitex/plt/ax/_plot/_stx_shaded_line.py +215 -0
- scitex/plt/ax/_plot/{_plot_violin.py → _stx_violin.py} +13 -6
- scitex/plt/ax/_style/__init__.py +3 -0
- scitex/plt/ax/_style/_style_barplot.py +13 -2
- scitex/plt/ax/_style/_style_boxplot.py +78 -32
- scitex/plt/ax/_style/_style_errorbar.py +17 -3
- scitex/plt/ax/_style/_style_scatter.py +17 -3
- scitex/plt/ax/_style/_style_violinplot.py +109 -0
- scitex/plt/color/_vizualize_colors.py +3 -3
- scitex/plt/styles/SCITEX_STYLE.yaml +104 -0
- scitex/plt/styles/__init__.py +57 -0
- scitex/plt/styles/_plot_defaults.py +209 -0
- scitex/plt/styles/_plot_postprocess.py +518 -0
- scitex/plt/styles/_style_loader.py +268 -0
- scitex/plt/styles/presets.py +208 -0
- scitex/plt/utils/_collect_figure_metadata.py +160 -18
- scitex/plt/utils/_colorbar.py +72 -10
- scitex/plt/utils/_configure_mpl.py +108 -52
- scitex/plt/utils/_crop.py +21 -7
- scitex/plt/utils/_figure_mm.py +21 -7
- scitex/stats/__init__.py +13 -1
- scitex/stats/_schema.py +578 -0
- scitex/stats/tests/__init__.py +13 -0
- scitex/stats/tests/correlation/__init__.py +13 -0
- scitex/stats/tests/correlation/_test_pearson.py +262 -0
- scitex/vis/__init__.py +6 -0
- scitex/vis/editor/__init__.py +23 -0
- scitex/vis/editor/_defaults.py +205 -0
- scitex/vis/editor/_edit.py +342 -0
- scitex/vis/editor/_mpl_editor.py +231 -0
- scitex/vis/editor/_tkinter_editor.py +466 -0
- scitex/vis/editor/_web_editor.py +1440 -0
- scitex/vis/model/plot_types.py +15 -15
- {scitex-2.3.0.dist-info → scitex-2.4.0.dist-info}/METADATA +2 -1
- {scitex-2.3.0.dist-info → scitex-2.4.0.dist-info}/RECORD +94 -67
- {scitex-2.3.0.dist-info → scitex-2.4.0.dist-info}/WHEEL +1 -1
- scitex/plt/ax/_plot/_plot_ecdf.py +0 -84
- scitex/plt/ax/_plot/_plot_heatmap.py +0 -277
- scitex/plt/ax/_plot/_plot_joyplot.py +0 -77
- scitex/plt/ax/_plot/_plot_shaded_line.py +0 -142
- scitex/plt/presets.py +0 -224
- {scitex-2.3.0.dist-info → scitex-2.4.0.dist-info}/entry_points.txt +0 -0
- {scitex-2.3.0.dist-info → scitex-2.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-12-01 20:00:00 (ywatanabe)"
|
|
4
|
+
# File: ./src/scitex/plt/styles/_style_loader.py
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Style loader for SciTeX plotting.
|
|
8
|
+
|
|
9
|
+
Loads style configuration from YAML file and provides centralized access
|
|
10
|
+
to all style parameters. This ensures consistency across all plotting functions.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
from scitex.plt.styles import load_style, get_style, STYLE
|
|
14
|
+
|
|
15
|
+
# Load default style
|
|
16
|
+
style = load_style()
|
|
17
|
+
fig, ax = stx.plt.subplots(**style)
|
|
18
|
+
|
|
19
|
+
# Load with journal preset
|
|
20
|
+
style = load_style(preset="nature")
|
|
21
|
+
|
|
22
|
+
# Load custom YAML file
|
|
23
|
+
style = load_style("path/to/my_style.yaml")
|
|
24
|
+
|
|
25
|
+
# Access individual style parameters
|
|
26
|
+
from scitex.plt.styles._style_loader import STYLE
|
|
27
|
+
line_width = STYLE.lines.trace_mm
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
__all__ = ["load_style", "get_style", "STYLE", "reload_style"]
|
|
31
|
+
|
|
32
|
+
import os
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Any, Dict, Optional, Union
|
|
35
|
+
|
|
36
|
+
import yaml
|
|
37
|
+
|
|
38
|
+
from scitex.dict import DotDict
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Path to default style file
|
|
42
|
+
_DEFAULT_STYLE_PATH = Path(__file__).parent / "SCITEX_STYLE.yaml"
|
|
43
|
+
|
|
44
|
+
# Global style cache
|
|
45
|
+
_STYLE_CACHE: Optional[DotDict] = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _deep_merge(base: Dict, override: Dict) -> Dict:
|
|
49
|
+
"""Deep merge two dictionaries, with override taking precedence."""
|
|
50
|
+
result = base.copy()
|
|
51
|
+
for key, value in override.items():
|
|
52
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
53
|
+
result[key] = _deep_merge(result[key], value)
|
|
54
|
+
else:
|
|
55
|
+
result[key] = value
|
|
56
|
+
return result
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _load_yaml(path: Union[str, Path]) -> Dict:
|
|
60
|
+
"""Load YAML file and return as dictionary."""
|
|
61
|
+
with open(path, "r") as f:
|
|
62
|
+
return yaml.safe_load(f)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def reload_style(
|
|
66
|
+
path: Optional[Union[str, Path]] = None,
|
|
67
|
+
preset: Optional[str] = None,
|
|
68
|
+
) -> DotDict:
|
|
69
|
+
"""
|
|
70
|
+
Reload style from YAML file (clears cache).
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
path : str or Path, optional
|
|
75
|
+
Path to YAML style file. If None, uses default SCITEX_STYLE.yaml
|
|
76
|
+
preset : str, optional
|
|
77
|
+
Journal preset to apply: "nature", "science", "cell", "pnas"
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
DotDict
|
|
82
|
+
Style configuration as DotDict for dot-access
|
|
83
|
+
"""
|
|
84
|
+
global _STYLE_CACHE
|
|
85
|
+
_STYLE_CACHE = None
|
|
86
|
+
return load_style(path, preset)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def load_style(
|
|
90
|
+
path: Optional[Union[str, Path]] = None,
|
|
91
|
+
preset: Optional[str] = None,
|
|
92
|
+
) -> DotDict:
|
|
93
|
+
"""
|
|
94
|
+
Load style configuration from YAML file.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
path : str or Path, optional
|
|
99
|
+
Path to YAML style file. If None, uses default SCITEX_STYLE.yaml
|
|
100
|
+
preset : str, optional
|
|
101
|
+
Journal preset to apply: "nature", "science", "cell", "pnas"
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
DotDict
|
|
106
|
+
Style configuration as DotDict for dot-access
|
|
107
|
+
|
|
108
|
+
Examples
|
|
109
|
+
--------
|
|
110
|
+
>>> style = load_style()
|
|
111
|
+
>>> style.fonts.axis_label_pt
|
|
112
|
+
7
|
|
113
|
+
>>> style.lines.trace_mm
|
|
114
|
+
0.2
|
|
115
|
+
"""
|
|
116
|
+
global _STYLE_CACHE
|
|
117
|
+
|
|
118
|
+
# Use cache if available and no custom path/preset
|
|
119
|
+
if _STYLE_CACHE is not None and path is None and preset is None:
|
|
120
|
+
return _STYLE_CACHE
|
|
121
|
+
|
|
122
|
+
# Load from file
|
|
123
|
+
style_path = Path(path) if path else _DEFAULT_STYLE_PATH
|
|
124
|
+
if not style_path.exists():
|
|
125
|
+
raise FileNotFoundError(f"Style file not found: {style_path}")
|
|
126
|
+
|
|
127
|
+
style_dict = _load_yaml(style_path)
|
|
128
|
+
|
|
129
|
+
# Apply preset if specified
|
|
130
|
+
if preset:
|
|
131
|
+
presets = style_dict.get("presets", {})
|
|
132
|
+
if preset not in presets:
|
|
133
|
+
available = list(presets.keys())
|
|
134
|
+
raise ValueError(f"Unknown preset '{preset}'. Available: {available}")
|
|
135
|
+
|
|
136
|
+
# Deep merge preset over base style
|
|
137
|
+
preset_overrides = presets[preset]
|
|
138
|
+
for section, values in preset_overrides.items():
|
|
139
|
+
if section in style_dict and isinstance(style_dict[section], dict):
|
|
140
|
+
style_dict[section] = _deep_merge(style_dict[section], values)
|
|
141
|
+
else:
|
|
142
|
+
style_dict[section] = values
|
|
143
|
+
|
|
144
|
+
# Remove presets section from final style
|
|
145
|
+
style_dict.pop("presets", None)
|
|
146
|
+
|
|
147
|
+
# Convert to DotDict for convenient access
|
|
148
|
+
style = DotDict(style_dict)
|
|
149
|
+
|
|
150
|
+
# Cache if using default
|
|
151
|
+
if path is None and preset is None:
|
|
152
|
+
_STYLE_CACHE = style
|
|
153
|
+
|
|
154
|
+
return style
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def get_style() -> DotDict:
|
|
158
|
+
"""
|
|
159
|
+
Get the current loaded style (loads default if not yet loaded).
|
|
160
|
+
|
|
161
|
+
Returns
|
|
162
|
+
-------
|
|
163
|
+
DotDict
|
|
164
|
+
Current style configuration
|
|
165
|
+
"""
|
|
166
|
+
global _STYLE_CACHE
|
|
167
|
+
if _STYLE_CACHE is None:
|
|
168
|
+
return load_style()
|
|
169
|
+
return _STYLE_CACHE
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def to_subplots_kwargs(style: Optional[DotDict] = None) -> Dict[str, Any]:
|
|
173
|
+
"""
|
|
174
|
+
Convert style DotDict to kwargs for stx.plt.subplots().
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
style : DotDict, optional
|
|
179
|
+
Style configuration. If None, uses current loaded style.
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
dict
|
|
184
|
+
Keyword arguments for stx.plt.subplots()
|
|
185
|
+
|
|
186
|
+
Examples
|
|
187
|
+
--------
|
|
188
|
+
>>> style = load_style(preset="nature")
|
|
189
|
+
>>> kwargs = to_subplots_kwargs(style)
|
|
190
|
+
>>> fig, ax = stx.plt.subplots(**kwargs)
|
|
191
|
+
"""
|
|
192
|
+
if style is None:
|
|
193
|
+
style = get_style()
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
# Axes dimensions
|
|
197
|
+
"ax_width_mm": style.axes.width_mm,
|
|
198
|
+
"ax_height_mm": style.axes.height_mm,
|
|
199
|
+
"ax_thickness_mm": style.axes.thickness_mm,
|
|
200
|
+
# Margins
|
|
201
|
+
"margin_left_mm": style.margins.left_mm,
|
|
202
|
+
"margin_right_mm": style.margins.right_mm,
|
|
203
|
+
"margin_bottom_mm": style.margins.bottom_mm,
|
|
204
|
+
"margin_top_mm": style.margins.top_mm,
|
|
205
|
+
# Spacing
|
|
206
|
+
"space_w_mm": style.spacing.horizontal_mm,
|
|
207
|
+
"space_h_mm": style.spacing.vertical_mm,
|
|
208
|
+
# Ticks
|
|
209
|
+
"tick_length_mm": style.ticks.length_mm,
|
|
210
|
+
"tick_thickness_mm": style.ticks.thickness_mm,
|
|
211
|
+
"n_ticks": style.ticks.n_ticks,
|
|
212
|
+
# Lines
|
|
213
|
+
"trace_thickness_mm": style.lines.trace_mm,
|
|
214
|
+
# Markers
|
|
215
|
+
"marker_size_mm": style.markers.size_mm,
|
|
216
|
+
# Fonts
|
|
217
|
+
"axis_font_size_pt": style.fonts.axis_label_pt,
|
|
218
|
+
"tick_font_size_pt": style.fonts.tick_label_pt,
|
|
219
|
+
"title_font_size_pt": style.fonts.title_pt,
|
|
220
|
+
"suptitle_font_size_pt": style.fonts.suptitle_pt,
|
|
221
|
+
"legend_font_size_pt": style.fonts.legend_pt,
|
|
222
|
+
# Padding
|
|
223
|
+
"label_pad_pt": style.padding.label_pt,
|
|
224
|
+
"tick_pad_pt": style.padding.tick_pt,
|
|
225
|
+
"title_pad_pt": style.padding.title_pt,
|
|
226
|
+
# Output
|
|
227
|
+
"dpi": style.output.dpi,
|
|
228
|
+
"transparent": style.output.transparent,
|
|
229
|
+
# Mode
|
|
230
|
+
"mode": "publication",
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
# Lazy-loaded global STYLE object
|
|
235
|
+
class _StyleProxy:
|
|
236
|
+
"""Proxy object that loads style on first access."""
|
|
237
|
+
|
|
238
|
+
def __getattr__(self, name: str) -> Any:
|
|
239
|
+
return getattr(get_style(), name)
|
|
240
|
+
|
|
241
|
+
def __repr__(self) -> str:
|
|
242
|
+
return repr(get_style())
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
STYLE = _StyleProxy()
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
if __name__ == "__main__":
|
|
249
|
+
# Test loading
|
|
250
|
+
print("Loading default style...")
|
|
251
|
+
style = load_style()
|
|
252
|
+
print(f" axes.width_mm: {style.axes.width_mm}")
|
|
253
|
+
print(f" fonts.axis_label_pt: {style.fonts.axis_label_pt}")
|
|
254
|
+
print(f" lines.trace_mm: {style.lines.trace_mm}")
|
|
255
|
+
|
|
256
|
+
print("\nLoading Nature preset...")
|
|
257
|
+
nature_style = load_style(preset="nature")
|
|
258
|
+
print(f" axes.width_mm: {nature_style.axes.width_mm}")
|
|
259
|
+
|
|
260
|
+
print("\nConverting to subplots kwargs...")
|
|
261
|
+
kwargs = to_subplots_kwargs()
|
|
262
|
+
for k, v in list(kwargs.items())[:5]:
|
|
263
|
+
print(f" {k}: {v}")
|
|
264
|
+
|
|
265
|
+
print("\nUsing STYLE proxy...")
|
|
266
|
+
print(f" STYLE.fonts.family: {STYLE.fonts.family}")
|
|
267
|
+
|
|
268
|
+
# EOF
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-12-01 21:30:00 (ywatanabe)"
|
|
4
|
+
# File: ./src/scitex/plt/styles/presets.py
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
SciTeX style configuration.
|
|
8
|
+
|
|
9
|
+
Style Control Levels:
|
|
10
|
+
1. Global level (subplots): axes dimensions, fonts, default line thickness
|
|
11
|
+
2. Plot level (per-call): override for individual ax.plot(), ax.scatter(), etc.
|
|
12
|
+
|
|
13
|
+
Priority cascade: direct → env → yaml → default
|
|
14
|
+
|
|
15
|
+
Style Management:
|
|
16
|
+
- load_style(path): Load style from YAML/JSON file
|
|
17
|
+
- save_style(path): Export current style to file
|
|
18
|
+
- set_style(style_dict): Change active style globally
|
|
19
|
+
- get_style(): Get current active style
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
from scitex.plt.styles import load_style, save_style, set_style
|
|
23
|
+
|
|
24
|
+
# Load and use custom style
|
|
25
|
+
style = load_style("my_style.yaml")
|
|
26
|
+
fig, ax = stx.plt.subplots(**style)
|
|
27
|
+
|
|
28
|
+
# Export current style
|
|
29
|
+
save_style("exported_style.yaml")
|
|
30
|
+
|
|
31
|
+
# Change global style
|
|
32
|
+
set_style({"axes_width_mm": 60, "trace_thickness_mm": 0.3})
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"SCITEX_STYLE", "STYLE",
|
|
37
|
+
"load_style", "save_style", "set_style", "get_style",
|
|
38
|
+
"resolve_style_value",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from typing import Any, Dict, Optional, Union
|
|
43
|
+
|
|
44
|
+
import scitex.io
|
|
45
|
+
from scitex.config import PriorityConfig
|
|
46
|
+
|
|
47
|
+
_STYLE_FILE = Path(__file__).parent / "SCITEX_STYLE.yaml"
|
|
48
|
+
_config: Optional[PriorityConfig] = None
|
|
49
|
+
_active_style: Optional[Dict[str, Any]] = None # User-set style override
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _get_config(yaml_path: Optional[Path] = None) -> PriorityConfig:
|
|
53
|
+
"""Get or create PriorityConfig from YAML."""
|
|
54
|
+
global _config
|
|
55
|
+
if _config is None or yaml_path:
|
|
56
|
+
yaml = scitex.io.load(yaml_path or _STYLE_FILE)
|
|
57
|
+
flat = {f"{k}.{k2}": v2 for k, v in yaml.items() if isinstance(v, dict) and k != "presets" for k2, v2 in v.items()}
|
|
58
|
+
_config = PriorityConfig(flat, env_prefix="SCITEX_PLT_", auto_uppercase=True)
|
|
59
|
+
return _config
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def resolve_style_value(key: str, direct_val: Any = None, default: Any = None, type: type = float) -> Any:
|
|
63
|
+
"""Resolve value with priority: direct → env → yaml → default.
|
|
64
|
+
|
|
65
|
+
Key format: 'axes.width_mm' - dots for YAML hierarchy, underscores for env vars.
|
|
66
|
+
Env var: SCITEX_PLT_AXES_WIDTH_MM (prefix + key with dots→underscores, uppercased)
|
|
67
|
+
"""
|
|
68
|
+
return _get_config().resolve(key, direct_val, default, type)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def load_style(path: Optional[Union[str, Path]] = None) -> Dict[str, Any]:
|
|
72
|
+
"""Load style from YAML as subplots kwargs."""
|
|
73
|
+
cfg = _get_config(Path(path) if path else None)
|
|
74
|
+
g = lambda k, d, t=float: cfg.resolve(k, None, d, t)
|
|
75
|
+
return {
|
|
76
|
+
"axes_width_mm": g("axes.width_mm", 40), "axes_height_mm": g("axes.height_mm", 28),
|
|
77
|
+
"axes_thickness_mm": g("axes.thickness_mm", 0.2),
|
|
78
|
+
"margin_left_mm": g("margins.left_mm", 20), "margin_right_mm": g("margins.right_mm", 20),
|
|
79
|
+
"margin_bottom_mm": g("margins.bottom_mm", 20), "margin_top_mm": g("margins.top_mm", 20),
|
|
80
|
+
"space_w_mm": g("spacing.horizontal_mm", 8), "space_h_mm": g("spacing.vertical_mm", 10),
|
|
81
|
+
"tick_length_mm": g("ticks.length_mm", 0.8), "tick_thickness_mm": g("ticks.thickness_mm", 0.2),
|
|
82
|
+
"n_ticks": g("ticks.n_ticks", 4, int),
|
|
83
|
+
"trace_thickness_mm": g("lines.trace_mm", 0.2), "errorbar_thickness_mm": g("lines.errorbar_mm", 0.2),
|
|
84
|
+
"errorbar_cap_width_mm": g("lines.errorbar_cap_mm", 0.8), "bar_edge_thickness_mm": g("lines.bar_edge_mm", 0.2),
|
|
85
|
+
"kde_line_thickness_mm": g("lines.kde_mm", 0.2),
|
|
86
|
+
"scatter_size_mm": g("markers.scatter_mm", 0.8), "marker_size_mm": g("markers.size_mm", 0.8),
|
|
87
|
+
"font_family": g("fonts.family", "Arial", str),
|
|
88
|
+
"axis_font_size_pt": g("fonts.axis_label_pt", 7), "tick_font_size_pt": g("fonts.tick_label_pt", 7),
|
|
89
|
+
"title_font_size_pt": g("fonts.title_pt", 8), "suptitle_font_size_pt": g("fonts.suptitle_pt", 8),
|
|
90
|
+
"legend_font_size_pt": g("fonts.legend_pt", 6), "annotation_font_size_pt": g("fonts.annotation_pt", 6),
|
|
91
|
+
"label_pad_pt": g("padding.label_pt", 0.5), "tick_pad_pt": g("padding.tick_pt", 2.0),
|
|
92
|
+
"title_pad_pt": g("padding.title_pt", 1.0),
|
|
93
|
+
"dpi": g("output.dpi", 300, int), "transparent": g("output.transparent", True, bool),
|
|
94
|
+
"auto_scale_axes": g("behavior.auto_scale_axes", True, bool), "mode": "publication",
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_style() -> Dict[str, Any]:
|
|
99
|
+
"""Get current active style configuration.
|
|
100
|
+
|
|
101
|
+
Returns style with priority: active_style → env → yaml → default
|
|
102
|
+
"""
|
|
103
|
+
global _active_style
|
|
104
|
+
base = load_style()
|
|
105
|
+
if _active_style:
|
|
106
|
+
base.update(_active_style)
|
|
107
|
+
return base
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def set_style(style_dict: Optional[Dict[str, Any]] = None) -> None:
|
|
111
|
+
"""Set active style globally.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
style_dict: Style values to override. Pass None to reset to defaults.
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
set_style({"axes_width_mm": 60, "trace_thickness_mm": 0.3})
|
|
118
|
+
set_style(None) # Reset to defaults
|
|
119
|
+
"""
|
|
120
|
+
global _active_style, SCITEX_STYLE, STYLE
|
|
121
|
+
_active_style = style_dict
|
|
122
|
+
SCITEX_STYLE = get_style()
|
|
123
|
+
STYLE = SCITEX_STYLE
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def save_style(path: Union[str, Path], style: Optional[Dict[str, Any]] = None) -> Path:
|
|
127
|
+
"""Export style to YAML or JSON file.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
path: Output file path (.yaml, .yml, or .json)
|
|
131
|
+
style: Style dict to export. If None, exports current active style.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Path to saved file.
|
|
135
|
+
|
|
136
|
+
Example:
|
|
137
|
+
save_style("my_style.yaml")
|
|
138
|
+
save_style("custom.json", {"axes_width_mm": 60})
|
|
139
|
+
"""
|
|
140
|
+
path = Path(path)
|
|
141
|
+
style = style or get_style()
|
|
142
|
+
|
|
143
|
+
# Convert flat style dict to hierarchical YAML structure
|
|
144
|
+
hierarchical = _flat_to_hierarchical(style)
|
|
145
|
+
|
|
146
|
+
scitex.io.save(hierarchical, path)
|
|
147
|
+
return path
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _flat_to_hierarchical(style: Dict[str, Any]) -> Dict[str, Any]:
|
|
151
|
+
"""Convert flat style dict to hierarchical YAML structure.
|
|
152
|
+
|
|
153
|
+
Example:
|
|
154
|
+
{"axes_width_mm": 40} -> {"axes": {"width_mm": 40}}
|
|
155
|
+
"""
|
|
156
|
+
# Mapping from flat keys to hierarchical paths
|
|
157
|
+
mapping = {
|
|
158
|
+
"axes_width_mm": ("axes", "width_mm"),
|
|
159
|
+
"axes_height_mm": ("axes", "height_mm"),
|
|
160
|
+
"axes_thickness_mm": ("axes", "thickness_mm"),
|
|
161
|
+
"margin_left_mm": ("margins", "left_mm"),
|
|
162
|
+
"margin_right_mm": ("margins", "right_mm"),
|
|
163
|
+
"margin_bottom_mm": ("margins", "bottom_mm"),
|
|
164
|
+
"margin_top_mm": ("margins", "top_mm"),
|
|
165
|
+
"space_w_mm": ("spacing", "horizontal_mm"),
|
|
166
|
+
"space_h_mm": ("spacing", "vertical_mm"),
|
|
167
|
+
"tick_length_mm": ("ticks", "length_mm"),
|
|
168
|
+
"tick_thickness_mm": ("ticks", "thickness_mm"),
|
|
169
|
+
"n_ticks": ("ticks", "n_ticks"),
|
|
170
|
+
"trace_thickness_mm": ("lines", "trace_mm"),
|
|
171
|
+
"errorbar_thickness_mm": ("lines", "errorbar_mm"),
|
|
172
|
+
"errorbar_cap_width_mm": ("lines", "errorbar_cap_mm"),
|
|
173
|
+
"bar_edge_thickness_mm": ("lines", "bar_edge_mm"),
|
|
174
|
+
"kde_line_thickness_mm": ("lines", "kde_mm"),
|
|
175
|
+
"scatter_size_mm": ("markers", "scatter_mm"),
|
|
176
|
+
"marker_size_mm": ("markers", "size_mm"),
|
|
177
|
+
"font_family": ("fonts", "family"),
|
|
178
|
+
"axis_font_size_pt": ("fonts", "axis_label_pt"),
|
|
179
|
+
"tick_font_size_pt": ("fonts", "tick_label_pt"),
|
|
180
|
+
"title_font_size_pt": ("fonts", "title_pt"),
|
|
181
|
+
"suptitle_font_size_pt": ("fonts", "suptitle_pt"),
|
|
182
|
+
"legend_font_size_pt": ("fonts", "legend_pt"),
|
|
183
|
+
"annotation_font_size_pt": ("fonts", "annotation_pt"),
|
|
184
|
+
"label_pad_pt": ("padding", "label_pt"),
|
|
185
|
+
"tick_pad_pt": ("padding", "tick_pt"),
|
|
186
|
+
"title_pad_pt": ("padding", "title_pt"),
|
|
187
|
+
"dpi": ("output", "dpi"),
|
|
188
|
+
"transparent": ("output", "transparent"),
|
|
189
|
+
"auto_scale_axes": ("behavior", "auto_scale_axes"),
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
result: Dict[str, Any] = {}
|
|
193
|
+
for flat_key, value in style.items():
|
|
194
|
+
if flat_key in mapping:
|
|
195
|
+
section, key = mapping[flat_key]
|
|
196
|
+
if section not in result:
|
|
197
|
+
result[section] = {}
|
|
198
|
+
result[section][key] = value
|
|
199
|
+
# Skip keys not in mapping (like 'mode')
|
|
200
|
+
|
|
201
|
+
return result
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
SCITEX_STYLE = load_style()
|
|
205
|
+
STYLE = SCITEX_STYLE
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# EOF
|