scitex 2.3.0__py3-none-any.whl → 2.4.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 (99) hide show
  1. scitex/ai/classification/reporters/reporter_utils/_Plotter.py +1 -1
  2. scitex/ai/plt/__init__.py +2 -2
  3. scitex/ai/plt/{_plot_conf_mat.py → _stx_conf_mat.py} +3 -3
  4. scitex/config/PriorityConfig.py +195 -0
  5. scitex/config/__init__.py +24 -0
  6. scitex/io/_save.py +125 -34
  7. scitex/io/_save_modules/_image.py +37 -20
  8. scitex/plt/__init__.py +470 -17
  9. scitex/plt/_subplots/_AxisWrapper.py +98 -50
  10. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +559 -124
  11. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +49 -8
  12. scitex/plt/_subplots/_SubplotsWrapper.py +76 -91
  13. scitex/plt/_subplots/_export_as_csv.py +127 -58
  14. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +25 -16
  15. scitex/plt/_subplots/_export_as_csv_formatters/_format_contourf.py +54 -0
  16. scitex/plt/_subplots/_export_as_csv_formatters/_format_hexbin.py +41 -0
  17. scitex/plt/_subplots/_export_as_csv_formatters/_format_hist2d.py +41 -0
  18. scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow.py +59 -47
  19. scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +42 -0
  20. scitex/plt/_subplots/_export_as_csv_formatters/_format_pie.py +42 -0
  21. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot.py +72 -35
  22. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_box.py +1 -1
  23. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +2 -2
  24. scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +53 -0
  25. scitex/plt/_subplots/_export_as_csv_formatters/_format_stem.py +42 -0
  26. scitex/plt/_subplots/_export_as_csv_formatters/_format_step.py +42 -0
  27. scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +48 -0
  28. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_conf_mat.py → _format_stx_conf_mat.py} +2 -2
  29. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_ecdf.py → _format_stx_ecdf.py} +2 -2
  30. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_fillv.py → _format_stx_fillv.py} +2 -2
  31. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_heatmap.py → _format_stx_heatmap.py} +2 -2
  32. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_image.py → _format_stx_image.py} +2 -2
  33. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_joyplot.py → _format_stx_joyplot.py} +2 -2
  34. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_line.py → _format_stx_line.py} +3 -3
  35. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_mean_ci.py → _format_stx_mean_ci.py} +2 -2
  36. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_mean_std.py → _format_stx_mean_std.py} +2 -2
  37. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_median_iqr.py → _format_stx_median_iqr.py} +2 -2
  38. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_raster.py → _format_stx_raster.py} +2 -2
  39. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_rectangle.py → _format_stx_rectangle.py} +1 -1
  40. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_scatter_hist.py → _format_stx_scatter_hist.py} +2 -2
  41. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_shaded_line.py → _format_stx_shaded_line.py} +2 -2
  42. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_violin.py → _format_stx_violin.py} +2 -2
  43. scitex/plt/_subplots/_export_as_csv_formatters/verify_formatters.py +23 -23
  44. scitex/plt/ax/__init__.py +16 -15
  45. scitex/plt/ax/_plot/__init__.py +30 -30
  46. scitex/plt/ax/_plot/_add_fitted_line.py +65 -11
  47. scitex/plt/ax/_plot/_plot_statistical_shaded_line.py +104 -76
  48. scitex/plt/ax/_plot/{_plot_conf_mat.py → _stx_conf_mat.py} +10 -10
  49. scitex/plt/ax/_plot/_stx_ecdf.py +109 -0
  50. scitex/plt/ax/_plot/{_plot_fillv.py → _stx_fillv.py} +7 -7
  51. scitex/plt/ax/_plot/_stx_heatmap.py +366 -0
  52. scitex/plt/ax/_plot/{_plot_image.py → _stx_image.py} +1 -1
  53. scitex/plt/ax/_plot/_stx_joyplot.py +113 -0
  54. scitex/plt/ax/_plot/{_plot_raster.py → _stx_raster.py} +37 -25
  55. scitex/plt/ax/_plot/{_plot_rectangle.py → _stx_rectangle.py} +10 -9
  56. scitex/plt/ax/_plot/{_plot_scatter_hist.py → _stx_scatter_hist.py} +1 -1
  57. scitex/plt/ax/_plot/_stx_shaded_line.py +215 -0
  58. scitex/plt/ax/_plot/{_plot_violin.py → _stx_violin.py} +13 -6
  59. scitex/plt/ax/_style/__init__.py +3 -0
  60. scitex/plt/ax/_style/_style_barplot.py +13 -2
  61. scitex/plt/ax/_style/_style_boxplot.py +78 -32
  62. scitex/plt/ax/_style/_style_errorbar.py +17 -3
  63. scitex/plt/ax/_style/_style_scatter.py +17 -3
  64. scitex/plt/ax/_style/_style_violinplot.py +109 -0
  65. scitex/plt/color/_vizualize_colors.py +3 -3
  66. scitex/plt/styles/SCITEX_STYLE.yaml +104 -0
  67. scitex/plt/styles/__init__.py +57 -0
  68. scitex/plt/styles/_plot_defaults.py +209 -0
  69. scitex/plt/styles/_plot_postprocess.py +518 -0
  70. scitex/plt/styles/_style_loader.py +268 -0
  71. scitex/plt/styles/presets.py +208 -0
  72. scitex/plt/utils/_collect_figure_metadata.py +160 -18
  73. scitex/plt/utils/_colorbar.py +72 -10
  74. scitex/plt/utils/_configure_mpl.py +108 -52
  75. scitex/plt/utils/_crop.py +21 -7
  76. scitex/plt/utils/_figure_mm.py +21 -7
  77. scitex/stats/__init__.py +13 -1
  78. scitex/stats/_schema.py +578 -0
  79. scitex/stats/tests/__init__.py +13 -0
  80. scitex/stats/tests/correlation/__init__.py +13 -0
  81. scitex/stats/tests/correlation/_test_pearson.py +262 -0
  82. scitex/vis/__init__.py +6 -0
  83. scitex/vis/editor/__init__.py +23 -0
  84. scitex/vis/editor/_defaults.py +205 -0
  85. scitex/vis/editor/_edit.py +342 -0
  86. scitex/vis/editor/_mpl_editor.py +231 -0
  87. scitex/vis/editor/_tkinter_editor.py +466 -0
  88. scitex/vis/editor/_web_editor.py +1440 -0
  89. scitex/vis/model/plot_types.py +15 -15
  90. {scitex-2.3.0.dist-info → scitex-2.4.1.dist-info}/METADATA +2 -1
  91. {scitex-2.3.0.dist-info → scitex-2.4.1.dist-info}/RECORD +94 -67
  92. {scitex-2.3.0.dist-info → scitex-2.4.1.dist-info}/WHEEL +1 -1
  93. scitex/plt/ax/_plot/_plot_ecdf.py +0 -84
  94. scitex/plt/ax/_plot/_plot_heatmap.py +0 -277
  95. scitex/plt/ax/_plot/_plot_joyplot.py +0 -77
  96. scitex/plt/ax/_plot/_plot_shaded_line.py +0 -142
  97. scitex/plt/presets.py +0 -224
  98. {scitex-2.3.0.dist-info → scitex-2.4.1.dist-info}/entry_points.txt +0 -0
  99. {scitex-2.3.0.dist-info → scitex-2.4.1.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