scitex 2.14.0__py3-none-any.whl → 2.15.3__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/__init__.py +71 -17
- scitex/_env_loader.py +156 -0
- scitex/_mcp_resources/__init__.py +37 -0
- scitex/_mcp_resources/_cheatsheet.py +135 -0
- scitex/_mcp_resources/_figrecipe.py +138 -0
- scitex/_mcp_resources/_formats.py +102 -0
- scitex/_mcp_resources/_modules.py +337 -0
- scitex/_mcp_resources/_session.py +149 -0
- scitex/_mcp_tools/__init__.py +4 -0
- scitex/_mcp_tools/audio.py +66 -0
- scitex/_mcp_tools/diagram.py +11 -95
- scitex/_mcp_tools/introspect.py +210 -0
- scitex/_mcp_tools/plt.py +260 -305
- scitex/_mcp_tools/scholar.py +74 -0
- scitex/_mcp_tools/social.py +27 -0
- scitex/_mcp_tools/template.py +24 -0
- scitex/_mcp_tools/writer.py +17 -210
- scitex/ai/_gen_ai/_PARAMS.py +10 -7
- scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
- scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
- scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
- scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
- scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
- scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
- scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
- scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
- scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
- scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
- scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
- scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
- scitex/audio/README.md +40 -36
- scitex/audio/__init__.py +129 -61
- scitex/audio/_branding.py +185 -0
- scitex/audio/_mcp/__init__.py +32 -0
- scitex/audio/_mcp/handlers.py +59 -6
- scitex/audio/_mcp/speak_handlers.py +238 -0
- scitex/audio/_relay.py +225 -0
- scitex/audio/_tts.py +18 -10
- scitex/audio/engines/base.py +17 -10
- scitex/audio/engines/elevenlabs_engine.py +7 -2
- scitex/audio/mcp_server.py +228 -75
- scitex/canvas/README.md +1 -1
- scitex/canvas/editor/_dearpygui/__init__.py +25 -0
- scitex/canvas/editor/_dearpygui/_editor.py +147 -0
- scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
- scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
- scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
- scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
- scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
- scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
- scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
- scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
- scitex/canvas/editor/_dearpygui/_selection.py +295 -0
- scitex/canvas/editor/_dearpygui/_state.py +93 -0
- scitex/canvas/editor/_dearpygui/_utils.py +61 -0
- scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
- scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
- scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
- scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
- scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
- scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
- scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
- scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
- scitex/canvas/editor/flask_editor/_core.py +25 -1684
- scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
- scitex/cli/__init__.py +38 -43
- scitex/cli/audio.py +160 -41
- scitex/cli/capture.py +133 -20
- scitex/cli/introspect.py +488 -0
- scitex/cli/main.py +200 -109
- scitex/cli/mcp.py +60 -34
- scitex/cli/plt.py +414 -0
- scitex/cli/repro.py +15 -8
- scitex/cli/resource.py +15 -8
- scitex/cli/scholar/__init__.py +154 -8
- scitex/cli/scholar/_crossref_scitex.py +296 -0
- scitex/cli/scholar/_fetch.py +25 -3
- scitex/cli/social.py +355 -0
- scitex/cli/stats.py +136 -11
- scitex/cli/template.py +129 -12
- scitex/cli/tex.py +15 -8
- scitex/cli/writer.py +49 -299
- scitex/cloud/__init__.py +41 -2
- scitex/config/README.md +1 -1
- scitex/config/__init__.py +16 -2
- scitex/config/_env_registry.py +256 -0
- scitex/context/__init__.py +22 -0
- scitex/dev/__init__.py +20 -1
- scitex/diagram/__init__.py +42 -19
- scitex/diagram/mcp_server.py +13 -125
- scitex/gen/__init__.py +50 -14
- scitex/gen/_list_packages.py +4 -4
- scitex/introspect/__init__.py +82 -0
- scitex/introspect/_call_graph.py +303 -0
- scitex/introspect/_class_hierarchy.py +163 -0
- scitex/introspect/_core.py +41 -0
- scitex/introspect/_docstring.py +131 -0
- scitex/introspect/_examples.py +113 -0
- scitex/introspect/_imports.py +271 -0
- scitex/{gen/_inspect_module.py → introspect/_list_api.py} +48 -56
- scitex/introspect/_mcp/__init__.py +41 -0
- scitex/introspect/_mcp/handlers.py +233 -0
- scitex/introspect/_members.py +155 -0
- scitex/introspect/_resolve.py +89 -0
- scitex/introspect/_signature.py +131 -0
- scitex/introspect/_source.py +80 -0
- scitex/introspect/_type_hints.py +172 -0
- scitex/io/_save.py +1 -2
- scitex/io/bundle/README.md +1 -1
- scitex/logging/_formatters.py +19 -9
- scitex/mcp_server.py +98 -5
- scitex/os/__init__.py +4 -0
- scitex/{gen → os}/_check_host.py +4 -5
- scitex/plt/__init__.py +245 -550
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
- scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/plt/gallery/README.md +1 -1
- scitex/plt/utils/_hitmap/__init__.py +82 -0
- scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
- scitex/plt/utils/_hitmap/_color_application.py +346 -0
- scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
- scitex/plt/utils/_hitmap/_constants.py +40 -0
- scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
- scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
- scitex/plt/utils/_hitmap/_query.py +113 -0
- scitex/plt/utils/_hitmap.py +46 -1616
- scitex/plt/utils/_metadata/__init__.py +80 -0
- scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
- scitex/plt/utils/_metadata/_artists/_base.py +195 -0
- scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
- scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
- scitex/plt/utils/_metadata/_artists/_images.py +80 -0
- scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
- scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
- scitex/plt/utils/_metadata/_artists/_text.py +106 -0
- scitex/plt/utils/_metadata/_csv.py +416 -0
- scitex/plt/utils/_metadata/_detect.py +225 -0
- scitex/plt/utils/_metadata/_legend.py +127 -0
- scitex/plt/utils/_metadata/_rounding.py +117 -0
- scitex/plt/utils/_metadata/_verification.py +202 -0
- scitex/schema/README.md +1 -1
- scitex/scholar/__init__.py +8 -0
- scitex/scholar/_mcp/crossref_handlers.py +265 -0
- scitex/scholar/core/Scholar.py +63 -1700
- scitex/scholar/core/_mixins/__init__.py +36 -0
- scitex/scholar/core/_mixins/_enrichers.py +270 -0
- scitex/scholar/core/_mixins/_library_handlers.py +100 -0
- scitex/scholar/core/_mixins/_loaders.py +103 -0
- scitex/scholar/core/_mixins/_pdf_download.py +375 -0
- scitex/scholar/core/_mixins/_pipeline.py +312 -0
- scitex/scholar/core/_mixins/_project_handlers.py +125 -0
- scitex/scholar/core/_mixins/_savers.py +69 -0
- scitex/scholar/core/_mixins/_search.py +103 -0
- scitex/scholar/core/_mixins/_services.py +88 -0
- scitex/scholar/core/_mixins/_url_finding.py +105 -0
- scitex/scholar/crossref_scitex.py +367 -0
- scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/scholar/examples/00_run_all.sh +120 -0
- scitex/scholar/jobs/_executors.py +27 -3
- scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
- scitex/scholar/pdf_download/_cli.py +154 -0
- scitex/scholar/pdf_download/strategies/__init__.py +11 -8
- scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
- scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
- scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
- scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
- scitex/scholar/pipelines/_single_steps.py +71 -36
- scitex/scholar/storage/_LibraryManager.py +97 -1695
- scitex/scholar/storage/_mixins/__init__.py +30 -0
- scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
- scitex/scholar/storage/_mixins/_library_operations.py +218 -0
- scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
- scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
- scitex/scholar/storage/_mixins/_resolution.py +376 -0
- scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
- scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
- scitex/security/README.md +3 -3
- scitex/session/README.md +1 -1
- scitex/session/__init__.py +26 -7
- scitex/session/_decorator.py +1 -1
- scitex/sh/README.md +1 -1
- scitex/sh/__init__.py +7 -4
- scitex/social/__init__.py +155 -0
- scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/stats/_mcp/_handlers/__init__.py +31 -0
- scitex/stats/_mcp/_handlers/_corrections.py +113 -0
- scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
- scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
- scitex/stats/_mcp/_handlers/_format.py +94 -0
- scitex/stats/_mcp/_handlers/_normality.py +110 -0
- scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
- scitex/stats/_mcp/_handlers/_power.py +247 -0
- scitex/stats/_mcp/_handlers/_recommend.py +102 -0
- scitex/stats/_mcp/_handlers/_run_test.py +279 -0
- scitex/stats/_mcp/_handlers/_stars.py +48 -0
- scitex/stats/_mcp/handlers.py +19 -1171
- scitex/stats/auto/_stat_style.py +175 -0
- scitex/stats/auto/_style_definitions.py +411 -0
- scitex/stats/auto/_styles.py +22 -620
- scitex/stats/descriptive/__init__.py +11 -8
- scitex/stats/descriptive/_ci.py +39 -0
- scitex/stats/power/_power.py +15 -4
- scitex/str/__init__.py +2 -1
- scitex/str/_title_case.py +63 -0
- scitex/template/README.md +1 -1
- scitex/template/__init__.py +25 -10
- scitex/template/_code_templates.py +147 -0
- scitex/template/_mcp/handlers.py +81 -0
- scitex/template/_mcp/tool_schemas.py +55 -0
- scitex/template/_templates/__init__.py +51 -0
- scitex/template/_templates/audio.py +233 -0
- scitex/template/_templates/canvas.py +312 -0
- scitex/template/_templates/capture.py +268 -0
- scitex/template/_templates/config.py +43 -0
- scitex/template/_templates/diagram.py +294 -0
- scitex/template/_templates/io.py +107 -0
- scitex/template/_templates/module.py +53 -0
- scitex/template/_templates/plt.py +202 -0
- scitex/template/_templates/scholar.py +267 -0
- scitex/template/_templates/session.py +130 -0
- scitex/template/_templates/session_minimal.py +43 -0
- scitex/template/_templates/session_plot.py +67 -0
- scitex/template/_templates/session_stats.py +77 -0
- scitex/template/_templates/stats.py +323 -0
- scitex/template/_templates/writer.py +296 -0
- scitex/template/clone_writer_directory.py +5 -5
- scitex/ui/_backends/_email.py +10 -2
- scitex/ui/_backends/_webhook.py +5 -1
- scitex/web/_search_pubmed.py +10 -6
- scitex/writer/README.md +1 -1
- scitex/writer/__init__.py +43 -34
- scitex/writer/_mcp/handlers.py +11 -744
- scitex/writer/_mcp/tool_schemas.py +5 -335
- scitex-2.15.3.dist-info/METADATA +667 -0
- {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/RECORD +241 -120
- scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
- scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
- scitex/diagram/_compile.py +0 -312
- scitex/diagram/_diagram.py +0 -355
- scitex/diagram/_mcp/__init__.py +0 -4
- scitex/diagram/_mcp/handlers.py +0 -400
- scitex/diagram/_mcp/tool_schemas.py +0 -157
- scitex/diagram/_presets.py +0 -173
- scitex/diagram/_schema.py +0 -182
- scitex/diagram/_split.py +0 -278
- scitex/gen/_ci.py +0 -12
- scitex/gen/_title_case.py +0 -89
- scitex/plt/_mcp/__init__.py +0 -4
- scitex/plt/_mcp/_handlers_annotation.py +0 -102
- scitex/plt/_mcp/_handlers_figure.py +0 -195
- scitex/plt/_mcp/_handlers_plot.py +0 -252
- scitex/plt/_mcp/_handlers_style.py +0 -219
- scitex/plt/_mcp/handlers.py +0 -74
- scitex/plt/_mcp/tool_schemas.py +0 -497
- scitex/plt/mcp_server.py +0 -231
- scitex/scholar/examples/SUGGESTIONS.md +0 -865
- scitex/scholar/examples/dev.py +0 -38
- scitex-2.14.0.dist-info/METADATA +0 -1238
- /scitex/{gen → context}/_detect_environment.py +0 -0
- /scitex/{gen → context}/_get_notebook_path.py +0 -0
- /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/WHEEL +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/entry_points.txt +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/licenses/LICENSE +0 -0
scitex/plt/__init__.py
CHANGED
|
@@ -1,46 +1,197 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# Timestamp: "
|
|
2
|
+
# Timestamp: "2026-01-19 (ywatanabe)"
|
|
3
3
|
# File: /home/ywatanabe/proj/scitex-code/src/scitex/plt/__init__.py
|
|
4
4
|
# ----------------------------------------
|
|
5
|
-
import os
|
|
6
|
-
|
|
7
|
-
__FILE__ = __file__
|
|
8
|
-
__DIR__ = os.path.dirname(__FILE__)
|
|
9
|
-
# ----------------------------------------
|
|
10
5
|
"""
|
|
11
|
-
SciTeX plt module - Publication-quality plotting.
|
|
6
|
+
SciTeX plt module - Publication-quality plotting via figrecipe.
|
|
12
7
|
|
|
8
|
+
This module provides a thin wrapper around figrecipe with scitex branding.
|
|
13
9
|
Simply importing this module automatically configures matplotlib with
|
|
14
|
-
SciTeX publication defaults
|
|
10
|
+
SciTeX publication defaults.
|
|
11
|
+
|
|
12
|
+
Usage
|
|
13
|
+
-----
|
|
14
|
+
>>> import scitex.plt as plt
|
|
15
|
+
>>> fig, ax = plt.subplots()
|
|
16
|
+
>>> ax.plot([1, 2, 3], [1, 4, 9])
|
|
17
|
+
>>> plt.save(fig, "figure.png")
|
|
18
|
+
|
|
19
|
+
Style Management
|
|
20
|
+
----------------
|
|
21
|
+
>>> plt.load_style("SCITEX") # Load publication style
|
|
22
|
+
>>> plt.STYLE # Access current style configuration
|
|
23
|
+
>>> plt.list_presets() # Show available presets
|
|
24
|
+
|
|
25
|
+
The module delegates to figrecipe for:
|
|
26
|
+
- Recording and reproducing figures
|
|
27
|
+
- Style management (mm-based layouts)
|
|
28
|
+
- Figure composition
|
|
29
|
+
- Graph visualization
|
|
30
|
+
|
|
31
|
+
SciTeX-specific features (kept locally):
|
|
32
|
+
- AxisWrapper/FigWrapper compatibility
|
|
33
|
+
- Color palettes (scitex.plt.color)
|
|
34
|
+
- Gallery utilities (scitex.plt.gallery)
|
|
35
|
+
"""
|
|
15
36
|
|
|
16
|
-
|
|
17
|
-
fig, ax = splt.subplots()
|
|
18
|
-
ax.plot([1, 2, 3], [1, 4, 9])
|
|
19
|
-
fig.savefig("figure.png")
|
|
37
|
+
import os
|
|
20
38
|
|
|
21
|
-
|
|
39
|
+
# ============================================================================
|
|
40
|
+
# Set branding environment variables BEFORE importing figrecipe
|
|
41
|
+
# This enables automatic docstring replacement: figrecipe -> scitex.plt, fr -> plt
|
|
42
|
+
# ============================================================================
|
|
43
|
+
os.environ.setdefault("FIGRECIPE_BRAND", "scitex.plt")
|
|
44
|
+
os.environ.setdefault("FIGRECIPE_ALIAS", "plt")
|
|
22
45
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
46
|
+
# ============================================================================
|
|
47
|
+
# Now import figrecipe (branding will be applied)
|
|
48
|
+
# ============================================================================
|
|
49
|
+
try:
|
|
50
|
+
import figrecipe as _fr
|
|
51
|
+
|
|
52
|
+
_FIGRECIPE_AVAILABLE = True
|
|
53
|
+
except ImportError:
|
|
54
|
+
_FIGRECIPE_AVAILABLE = False
|
|
55
|
+
_fr = None
|
|
28
56
|
|
|
57
|
+
# Standard library and matplotlib imports
|
|
29
58
|
import matplotlib as mpl
|
|
30
59
|
import matplotlib.font_manager as fm
|
|
31
|
-
import matplotlib.pyplot as
|
|
60
|
+
import matplotlib.pyplot as _plt
|
|
32
61
|
|
|
33
62
|
from scitex import logging as _logging
|
|
34
63
|
|
|
35
64
|
_logger = _logging.getLogger(__name__)
|
|
36
65
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
66
|
+
__FILE__ = __file__
|
|
67
|
+
__DIR__ = os.path.dirname(__FILE__)
|
|
68
|
+
|
|
69
|
+
# ============================================================================
|
|
70
|
+
# Re-export figrecipe public API with scitex branding
|
|
71
|
+
# ============================================================================
|
|
72
|
+
if _FIGRECIPE_AVAILABLE:
|
|
73
|
+
# Core public API
|
|
74
|
+
from figrecipe import __version__ as _figrecipe_version
|
|
75
|
+
from figrecipe import (
|
|
76
|
+
compose,
|
|
77
|
+
crop,
|
|
78
|
+
edit,
|
|
79
|
+
extract_data,
|
|
80
|
+
info,
|
|
81
|
+
list_presets,
|
|
82
|
+
load_style,
|
|
83
|
+
reproduce,
|
|
84
|
+
save,
|
|
85
|
+
subplots,
|
|
86
|
+
unload_style,
|
|
87
|
+
validate,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Internal imports (not part of figrecipe public API)
|
|
91
|
+
from figrecipe._api._notebook import enable_svg
|
|
92
|
+
from figrecipe._api._seaborn_proxy import sns
|
|
93
|
+
from figrecipe._api._style_manager import STYLE, apply_style
|
|
94
|
+
from figrecipe._composition import align_panels, distribute_panels, smart_align
|
|
95
|
+
from figrecipe._graph_presets import get_preset as get_graph_preset
|
|
96
|
+
from figrecipe._graph_presets import list_presets as list_graph_presets
|
|
97
|
+
from figrecipe._graph_presets import register_preset as register_graph_preset
|
|
98
|
+
|
|
99
|
+
# Also export load as alias for reproduce
|
|
100
|
+
load = reproduce
|
|
101
|
+
else:
|
|
102
|
+
# Provide stub versions when figrecipe is not available
|
|
103
|
+
_figrecipe_version = "0.0.0"
|
|
104
|
+
|
|
105
|
+
def _not_available(*args, **kwargs):
|
|
106
|
+
raise ImportError(
|
|
107
|
+
"figrecipe is required for this feature. Install with: pip install figrecipe"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
STYLE = None
|
|
111
|
+
load_style = _not_available
|
|
112
|
+
unload_style = _not_available
|
|
113
|
+
list_presets = _not_available
|
|
114
|
+
apply_style = _not_available
|
|
115
|
+
subplots = _not_available
|
|
116
|
+
save = _not_available
|
|
117
|
+
reproduce = _not_available
|
|
118
|
+
load = _not_available
|
|
119
|
+
crop = _not_available
|
|
120
|
+
validate = _not_available
|
|
121
|
+
extract_data = _not_available
|
|
122
|
+
info = _not_available
|
|
123
|
+
edit = _not_available
|
|
124
|
+
compose = _not_available
|
|
125
|
+
align_panels = _not_available
|
|
126
|
+
distribute_panels = _not_available
|
|
127
|
+
smart_align = _not_available
|
|
128
|
+
sns = None
|
|
129
|
+
enable_svg = _not_available
|
|
130
|
+
get_graph_preset = _not_available
|
|
131
|
+
list_graph_presets = _not_available
|
|
132
|
+
register_graph_preset = _not_available
|
|
133
|
+
|
|
134
|
+
# ============================================================================
|
|
135
|
+
# Local scitex submodules (kept for compatibility)
|
|
136
|
+
# ============================================================================
|
|
137
|
+
try:
|
|
138
|
+
from ._tpl import termplot
|
|
139
|
+
except ImportError:
|
|
140
|
+
termplot = None
|
|
141
|
+
|
|
142
|
+
# Backward compatibility: expose styles submodule (deprecated, use figrecipe)
|
|
143
|
+
from . import ax, color, gallery, styles, utils
|
|
144
|
+
|
|
145
|
+
# Import draw_graph from figrecipe integration (handles AxisWrapper)
|
|
146
|
+
from ._figrecipe_integration import draw_graph
|
|
147
|
+
from .styles import presets
|
|
148
|
+
|
|
149
|
+
# ============================================================================
|
|
150
|
+
# Auto-configure matplotlib with SciTeX defaults on import
|
|
151
|
+
# ============================================================================
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _register_arial_fonts():
|
|
155
|
+
"""Register Arial fonts if available."""
|
|
156
|
+
try:
|
|
157
|
+
fm.findfont("Arial", fallback_to_default=False)
|
|
158
|
+
return True
|
|
159
|
+
except Exception:
|
|
160
|
+
# Search for Arial font files and register them
|
|
161
|
+
arial_paths = [
|
|
162
|
+
f
|
|
163
|
+
for f in fm.findSystemFonts()
|
|
164
|
+
if os.path.basename(f).lower().startswith("arial")
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
if arial_paths:
|
|
168
|
+
for path in arial_paths:
|
|
169
|
+
try:
|
|
170
|
+
fm.fontManager.addfont(path)
|
|
171
|
+
except Exception:
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
# Verify Arial is now available
|
|
175
|
+
try:
|
|
176
|
+
fm.findfont("Arial", fallback_to_default=False)
|
|
177
|
+
return True
|
|
178
|
+
except Exception:
|
|
179
|
+
pass
|
|
180
|
+
return False
|
|
40
181
|
|
|
41
182
|
|
|
42
183
|
def _auto_configure_mpl():
|
|
43
184
|
"""Apply SciTeX style configuration automatically on import."""
|
|
185
|
+
# Try to use figrecipe's style system first
|
|
186
|
+
if _FIGRECIPE_AVAILABLE:
|
|
187
|
+
try:
|
|
188
|
+
# Load SCITEX style preset from figrecipe
|
|
189
|
+
load_style("SCITEX")
|
|
190
|
+
return
|
|
191
|
+
except Exception:
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
# Fallback: use local style loader
|
|
44
195
|
from .styles import resolve_style_value
|
|
45
196
|
|
|
46
197
|
# mm to pt conversion factor
|
|
@@ -111,32 +262,8 @@ def _auto_configure_mpl():
|
|
|
111
262
|
mpl.rcParams.update(mpl_config)
|
|
112
263
|
|
|
113
264
|
|
|
114
|
-
# Register Arial fonts eagerly
|
|
115
|
-
_arial_enabled =
|
|
116
|
-
try:
|
|
117
|
-
fm.findfont("Arial", fallback_to_default=False)
|
|
118
|
-
_arial_enabled = True
|
|
119
|
-
except Exception:
|
|
120
|
-
# Search for Arial font files and register them
|
|
121
|
-
arial_paths = [
|
|
122
|
-
f
|
|
123
|
-
for f in fm.findSystemFonts()
|
|
124
|
-
if os.path.basename(f).lower().startswith("arial")
|
|
125
|
-
]
|
|
126
|
-
|
|
127
|
-
if arial_paths:
|
|
128
|
-
for path in arial_paths:
|
|
129
|
-
try:
|
|
130
|
-
fm.fontManager.addfont(path)
|
|
131
|
-
except Exception:
|
|
132
|
-
pass
|
|
133
|
-
|
|
134
|
-
# Verify Arial is now available
|
|
135
|
-
try:
|
|
136
|
-
fm.findfont("Arial", fallback_to_default=False)
|
|
137
|
-
_arial_enabled = True
|
|
138
|
-
except Exception:
|
|
139
|
-
pass
|
|
265
|
+
# Register Arial fonts eagerly
|
|
266
|
+
_arial_enabled = _register_arial_fonts()
|
|
140
267
|
|
|
141
268
|
# Configure font family
|
|
142
269
|
if _arial_enabled:
|
|
@@ -165,458 +292,35 @@ _auto_configure_mpl()
|
|
|
165
292
|
|
|
166
293
|
# Set up color cycle from scitex colors
|
|
167
294
|
try:
|
|
168
|
-
from . import color as _color_module
|
|
169
|
-
|
|
170
295
|
_rgba_norm_cycle = {
|
|
171
|
-
k: tuple(
|
|
172
|
-
for k, v in
|
|
296
|
+
k: tuple(color.update_alpha(v, 1.0))
|
|
297
|
+
for k, v in color.PARAMS.get("RGBA_NORM_FOR_CYCLE", {}).items()
|
|
173
298
|
}
|
|
174
299
|
if _rgba_norm_cycle:
|
|
175
|
-
mpl.rcParams["axes.prop_cycle"] =
|
|
300
|
+
mpl.rcParams["axes.prop_cycle"] = _plt.cycler(
|
|
176
301
|
color=list(_rgba_norm_cycle.values())
|
|
177
302
|
)
|
|
178
303
|
except Exception:
|
|
179
304
|
pass # Use matplotlib default colors if color module fails
|
|
180
305
|
|
|
181
|
-
try:
|
|
182
|
-
from ._tpl import termplot
|
|
183
|
-
except ImportError:
|
|
184
|
-
termplot = None
|
|
185
|
-
from . import ax, color, gallery, styles, utils
|
|
186
|
-
|
|
187
|
-
# Figrecipe integration (graph visualization, editor)
|
|
188
|
-
from ._figrecipe_integration import draw_graph, edit
|
|
189
|
-
from .styles import presets
|
|
190
|
-
|
|
191
|
-
# Lazy import for subplots to avoid circular dependencies
|
|
192
|
-
# Note: Use names that don't conflict with submodule names like _subplots
|
|
193
|
-
_subplots_func_cached = None
|
|
194
|
-
_figure_func_cached = None
|
|
195
|
-
_crop_func_cached = None
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def subplots(*args, **kwargs):
|
|
199
|
-
"""Lazy-loaded subplots function."""
|
|
200
|
-
global _subplots_func_cached
|
|
201
|
-
if _subplots_func_cached is None:
|
|
202
|
-
from ._subplots._SubplotsWrapper import subplots as _subplots_impl
|
|
203
306
|
|
|
204
|
-
|
|
205
|
-
|
|
307
|
+
# ============================================================================
|
|
308
|
+
# SciTeX-specific wrapper functions (for AxisWrapper/FigWrapper compatibility)
|
|
309
|
+
# ============================================================================
|
|
206
310
|
|
|
207
311
|
|
|
208
312
|
def figure(*args, **kwargs):
|
|
209
|
-
"""
|
|
210
|
-
global _figure_func_cached
|
|
211
|
-
if _figure_func_cached is None:
|
|
212
|
-
import matplotlib.pyplot as plt
|
|
213
|
-
|
|
214
|
-
from ._subplots._FigWrapper import FigWrapper
|
|
215
|
-
|
|
216
|
-
def _figure_impl(*args, **kwargs):
|
|
217
|
-
fig_mpl = plt.figure(*args, **kwargs)
|
|
218
|
-
return FigWrapper(fig_mpl)
|
|
313
|
+
"""Create a figure that returns a FigWrapper.
|
|
219
314
|
|
|
220
|
-
|
|
221
|
-
|
|
315
|
+
This is the scitex-specific figure function that creates FigWrapper
|
|
316
|
+
objects for compatibility with scitex.plt.ax utilities.
|
|
222
317
|
|
|
223
|
-
|
|
224
|
-
def crop(input_path, output_path=None, margin=12, overwrite=False, verbose=False):
|
|
318
|
+
For figrecipe-style recording figures, use subplots() instead.
|
|
225
319
|
"""
|
|
226
|
-
|
|
320
|
+
from ._subplots._FigWrapper import FigWrapper
|
|
227
321
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
created with large margins.
|
|
231
|
-
|
|
232
|
-
Parameters
|
|
233
|
-
----------
|
|
234
|
-
input_path : str
|
|
235
|
-
Path to the input image
|
|
236
|
-
output_path : str, optional
|
|
237
|
-
Path to save cropped image. If None and overwrite=True, overwrites input.
|
|
238
|
-
If None and overwrite=False, adds '_cropped' suffix.
|
|
239
|
-
margin : int, optional
|
|
240
|
-
Margin in pixels around content (default: 12, ~1mm at 300 DPI)
|
|
241
|
-
overwrite : bool, optional
|
|
242
|
-
Overwrite input file (default: False)
|
|
243
|
-
verbose : bool, optional
|
|
244
|
-
Print detailed information (default: False)
|
|
245
|
-
|
|
246
|
-
Returns
|
|
247
|
-
-------
|
|
248
|
-
str
|
|
249
|
-
Path to the saved cropped image
|
|
250
|
-
|
|
251
|
-
Examples
|
|
252
|
-
--------
|
|
253
|
-
>>> fig, ax = stx.plt.subplots(**stx.plt.presets.SCITEX_STYLE)
|
|
254
|
-
>>> ax.plot([1, 2, 3], [1, 2, 3])
|
|
255
|
-
>>> stx.io.save(fig, "figure.png")
|
|
256
|
-
>>> stx.plt.crop("figure.png", "figure_cropped.png") # 1mm margin
|
|
257
|
-
"""
|
|
258
|
-
global _crop_func_cached
|
|
259
|
-
if _crop_func_cached is None:
|
|
260
|
-
from .utils._crop import crop as _crop_impl
|
|
261
|
-
|
|
262
|
-
_crop_func_cached = _crop_impl
|
|
263
|
-
return _crop_func_cached(input_path, output_path, margin, overwrite, verbose)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def load(path, apply_manual=True):
|
|
267
|
-
"""
|
|
268
|
-
Load a figure from saved JSON + CSV files.
|
|
269
|
-
|
|
270
|
-
Parameters
|
|
271
|
-
----------
|
|
272
|
-
path : str or Path
|
|
273
|
-
Path to JSON file, PNG file, or CSV file.
|
|
274
|
-
Will auto-detect sibling files in same directory or organized subdirectories.
|
|
275
|
-
apply_manual : bool, optional
|
|
276
|
-
If True, apply .manual.json overrides if exists (default: True)
|
|
277
|
-
|
|
278
|
-
Returns
|
|
279
|
-
-------
|
|
280
|
-
tuple
|
|
281
|
-
(fig, axes) where fig is FigWrapper and axes is AxisWrapper or array
|
|
282
|
-
|
|
283
|
-
Raises
|
|
284
|
-
------
|
|
285
|
-
FileNotFoundError
|
|
286
|
-
If required JSON file is not found
|
|
287
|
-
ValueError
|
|
288
|
-
If manual.json hash doesn't match (stale manual edits)
|
|
289
|
-
|
|
290
|
-
Examples
|
|
291
|
-
--------
|
|
292
|
-
>>> # Load from JSON (sibling pattern)
|
|
293
|
-
>>> fig, axes = stx.plt.load("output/figure.json")
|
|
294
|
-
|
|
295
|
-
>>> # Load from PNG (finds sibling JSON + CSV)
|
|
296
|
-
>>> fig, axes = stx.plt.load("output/figure.png")
|
|
297
|
-
|
|
298
|
-
>>> # Load from organized directory pattern
|
|
299
|
-
>>> fig, axes = stx.plt.load("output/json/figure.json")
|
|
300
|
-
|
|
301
|
-
>>> # Skip manual overrides
|
|
302
|
-
>>> fig, axes = stx.plt.load("figure.json", apply_manual=False)
|
|
303
|
-
|
|
304
|
-
Notes
|
|
305
|
-
-----
|
|
306
|
-
Supports two directory patterns:
|
|
307
|
-
|
|
308
|
-
Pattern 1 (flat/sibling):
|
|
309
|
-
output/figure.png
|
|
310
|
-
output/figure.json
|
|
311
|
-
output/figure.csv
|
|
312
|
-
|
|
313
|
-
Pattern 2 (organized):
|
|
314
|
-
output/png/figure.png
|
|
315
|
-
output/json/figure.json
|
|
316
|
-
output/csv/figure.csv
|
|
317
|
-
|
|
318
|
-
Manual overrides (.manual.json) are applied if:
|
|
319
|
-
- apply_manual=True
|
|
320
|
-
- figure.manual.json exists alongside figure.json
|
|
321
|
-
- Hash validation passes (warns if stale)
|
|
322
|
-
"""
|
|
323
|
-
import hashlib
|
|
324
|
-
from pathlib import Path
|
|
325
|
-
|
|
326
|
-
import scitex as stx
|
|
327
|
-
|
|
328
|
-
path = Path(path)
|
|
329
|
-
|
|
330
|
-
# Resolve JSON path from any input (png, csv, or json)
|
|
331
|
-
json_path, csv_path = _resolve_figure_paths(path)
|
|
332
|
-
|
|
333
|
-
if not json_path.exists():
|
|
334
|
-
raise FileNotFoundError(f"JSON file not found: {json_path}")
|
|
335
|
-
|
|
336
|
-
# Load JSON metadata
|
|
337
|
-
metadata = stx.io.load(json_path)
|
|
338
|
-
|
|
339
|
-
# Load CSV data if exists
|
|
340
|
-
csv_data = None
|
|
341
|
-
if csv_path and csv_path.exists():
|
|
342
|
-
csv_data = stx.io.load(csv_path)
|
|
343
|
-
|
|
344
|
-
# Check for manual overrides
|
|
345
|
-
manual_path = json_path.with_suffix(".manual.json")
|
|
346
|
-
manual_overrides = None
|
|
347
|
-
if apply_manual and manual_path.exists():
|
|
348
|
-
manual_data = stx.io.load(manual_path)
|
|
349
|
-
|
|
350
|
-
# Validate hash
|
|
351
|
-
if "base_hash" in manual_data:
|
|
352
|
-
current_hash = _compute_file_hash(json_path)
|
|
353
|
-
if manual_data["base_hash"] != current_hash:
|
|
354
|
-
_logger.warning(
|
|
355
|
-
f"Manual overrides may be stale: base data changed since manual edits.\n"
|
|
356
|
-
f" Expected hash: {manual_data['base_hash'][:16]}...\n"
|
|
357
|
-
f" Current hash: {current_hash[:16]}...\n"
|
|
358
|
-
f" Review: {manual_path}"
|
|
359
|
-
)
|
|
360
|
-
manual_overrides = manual_data.get("overrides", {})
|
|
361
|
-
|
|
362
|
-
# Reconstruct figure
|
|
363
|
-
fig, axes = _reconstruct_figure(metadata, csv_data, manual_overrides)
|
|
364
|
-
|
|
365
|
-
return fig, axes
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
def _resolve_figure_paths(path):
|
|
369
|
-
"""
|
|
370
|
-
Resolve JSON and CSV paths from any input file path.
|
|
371
|
-
|
|
372
|
-
Supports both flat (sibling) and organized (subdirectory) patterns.
|
|
373
|
-
"""
|
|
374
|
-
from pathlib import Path
|
|
375
|
-
|
|
376
|
-
path = Path(path)
|
|
377
|
-
stem = path.stem
|
|
378
|
-
suffix = path.suffix.lower()
|
|
379
|
-
parent = path.parent
|
|
380
|
-
|
|
381
|
-
# Determine base name (remove .manual if present)
|
|
382
|
-
if stem.endswith(".manual"):
|
|
383
|
-
stem = stem[:-7]
|
|
384
|
-
|
|
385
|
-
json_path = None
|
|
386
|
-
csv_path = None
|
|
387
|
-
|
|
388
|
-
if suffix == ".json":
|
|
389
|
-
json_path = path
|
|
390
|
-
# Try sibling CSV first
|
|
391
|
-
csv_sibling = parent / f"{stem}.csv"
|
|
392
|
-
if csv_sibling.exists():
|
|
393
|
-
csv_path = csv_sibling
|
|
394
|
-
# Try organized pattern (../csv/)
|
|
395
|
-
elif parent.name == "json":
|
|
396
|
-
csv_organized = parent.parent / "csv" / f"{stem}.csv"
|
|
397
|
-
if csv_organized.exists():
|
|
398
|
-
csv_path = csv_organized
|
|
399
|
-
|
|
400
|
-
elif suffix in (".png", ".jpg", ".jpeg", ".pdf", ".svg"):
|
|
401
|
-
# Look for sibling JSON
|
|
402
|
-
json_sibling = parent / f"{stem}.json"
|
|
403
|
-
csv_sibling = parent / f"{stem}.csv"
|
|
404
|
-
|
|
405
|
-
if json_sibling.exists():
|
|
406
|
-
json_path = json_sibling
|
|
407
|
-
if csv_sibling.exists():
|
|
408
|
-
csv_path = csv_sibling
|
|
409
|
-
# Try organized pattern (parent has png/, look for json/)
|
|
410
|
-
elif parent.name in ("png", "jpg", "jpeg", "pdf", "svg"):
|
|
411
|
-
json_organized = parent.parent / "json" / f"{stem}.json"
|
|
412
|
-
csv_organized = parent.parent / "csv" / f"{stem}.csv"
|
|
413
|
-
if json_organized.exists():
|
|
414
|
-
json_path = json_organized
|
|
415
|
-
if csv_organized.exists():
|
|
416
|
-
csv_path = csv_organized
|
|
417
|
-
|
|
418
|
-
elif suffix == ".csv":
|
|
419
|
-
csv_path = path
|
|
420
|
-
# Try sibling JSON
|
|
421
|
-
json_sibling = parent / f"{stem}.json"
|
|
422
|
-
if json_sibling.exists():
|
|
423
|
-
json_path = json_sibling
|
|
424
|
-
# Try organized pattern (../json/)
|
|
425
|
-
elif parent.name == "csv":
|
|
426
|
-
json_organized = parent.parent / "json" / f"{stem}.json"
|
|
427
|
-
if json_organized.exists():
|
|
428
|
-
json_path = json_organized
|
|
429
|
-
|
|
430
|
-
# Fallback: assume it's the JSON path
|
|
431
|
-
if json_path is None:
|
|
432
|
-
json_path = path if suffix == ".json" else path.with_suffix(".json")
|
|
433
|
-
|
|
434
|
-
return json_path, csv_path
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
def _compute_file_hash(path):
|
|
438
|
-
"""Compute SHA256 hash of a file."""
|
|
439
|
-
import hashlib
|
|
440
|
-
from pathlib import Path
|
|
441
|
-
|
|
442
|
-
path = Path(path)
|
|
443
|
-
sha256 = hashlib.sha256()
|
|
444
|
-
with open(path, "rb") as f:
|
|
445
|
-
for chunk in iter(lambda: f.read(8192), b""):
|
|
446
|
-
sha256.update(chunk)
|
|
447
|
-
return f"sha256:{sha256.hexdigest()}"
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
def _reconstruct_figure(metadata, csv_data, manual_overrides=None):
|
|
451
|
-
"""
|
|
452
|
-
Reconstruct figure from metadata and CSV data.
|
|
453
|
-
|
|
454
|
-
Parameters
|
|
455
|
-
----------
|
|
456
|
-
metadata : dict
|
|
457
|
-
JSON metadata from stx.io.save()
|
|
458
|
-
csv_data : DataFrame or None
|
|
459
|
-
CSV data with plot values
|
|
460
|
-
manual_overrides : dict or None
|
|
461
|
-
Manual style/annotation overrides
|
|
462
|
-
|
|
463
|
-
Returns
|
|
464
|
-
-------
|
|
465
|
-
tuple
|
|
466
|
-
(fig, axes)
|
|
467
|
-
"""
|
|
468
|
-
import numpy as np
|
|
469
|
-
|
|
470
|
-
# Extract dimensions from metadata
|
|
471
|
-
dims = metadata.get("dimensions", {})
|
|
472
|
-
fig_size_mm = dims.get("figure_size_mm", [80, 68])
|
|
473
|
-
dpi = dims.get("dpi", 300)
|
|
474
|
-
|
|
475
|
-
# Get style from metadata
|
|
476
|
-
scitex_meta = metadata.get("scitex", {})
|
|
477
|
-
style_mm = scitex_meta.get("style_mm", {})
|
|
478
|
-
|
|
479
|
-
# Create figure with same dimensions
|
|
480
|
-
fig, axes = subplots(
|
|
481
|
-
axes_width_mm=style_mm.get(
|
|
482
|
-
"axes_width_mm", dims.get("axes_size_mm", [40, 28])[0]
|
|
483
|
-
),
|
|
484
|
-
axes_height_mm=style_mm.get(
|
|
485
|
-
"axes_height_mm", dims.get("axes_size_mm", [40, 28])[1]
|
|
486
|
-
),
|
|
487
|
-
dpi=dpi,
|
|
488
|
-
)
|
|
489
|
-
|
|
490
|
-
# Handle single vs multiple axes
|
|
491
|
-
ax = axes if not hasattr(axes, "flat") else list(axes.flat)[0]
|
|
492
|
-
|
|
493
|
-
# Set axis labels from metadata
|
|
494
|
-
axes_meta = metadata.get("axes", {})
|
|
495
|
-
x_meta = axes_meta.get("x", {})
|
|
496
|
-
y_meta = axes_meta.get("y", {})
|
|
497
|
-
|
|
498
|
-
xlabel = x_meta.get("label", "")
|
|
499
|
-
ylabel = y_meta.get("label", "")
|
|
500
|
-
x_unit = x_meta.get("unit", "")
|
|
501
|
-
y_unit = y_meta.get("unit", "")
|
|
502
|
-
|
|
503
|
-
if xlabel:
|
|
504
|
-
full_xlabel = f"{xlabel} [{x_unit}]" if x_unit else xlabel
|
|
505
|
-
ax.set_xlabel(full_xlabel)
|
|
506
|
-
if ylabel:
|
|
507
|
-
full_ylabel = f"{ylabel} [{y_unit}]" if y_unit else ylabel
|
|
508
|
-
ax.set_ylabel(full_ylabel)
|
|
509
|
-
|
|
510
|
-
# Reconstruct plots from CSV data
|
|
511
|
-
if csv_data is not None and not csv_data.empty:
|
|
512
|
-
_reconstruct_plots_from_csv(ax, csv_data, metadata)
|
|
513
|
-
|
|
514
|
-
# Apply manual overrides
|
|
515
|
-
if manual_overrides:
|
|
516
|
-
_apply_manual_overrides(fig, axes, manual_overrides)
|
|
517
|
-
|
|
518
|
-
return fig, axes
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
def _reconstruct_plots_from_csv(ax, csv_data, metadata):
|
|
522
|
-
"""
|
|
523
|
-
Reconstruct plot elements from CSV data.
|
|
524
|
-
|
|
525
|
-
CSV columns follow pattern: ax_00_<type>_<name>
|
|
526
|
-
"""
|
|
527
|
-
import numpy as np
|
|
528
|
-
import pandas as pd
|
|
529
|
-
|
|
530
|
-
# Group columns by plot type
|
|
531
|
-
plot_type = metadata.get("plot_type", metadata.get("method", "line"))
|
|
532
|
-
|
|
533
|
-
# Parse column names to find plot data
|
|
534
|
-
columns = csv_data.columns.tolist()
|
|
535
|
-
|
|
536
|
-
# Find x/y data columns
|
|
537
|
-
x_cols = [c for c in columns if "_x" in c.lower() and "text" not in c.lower()]
|
|
538
|
-
y_cols = [c for c in columns if "_y" in c.lower() and "text" not in c.lower()]
|
|
539
|
-
|
|
540
|
-
if plot_type == "line" or plot_type == "plot":
|
|
541
|
-
# For line plots, look for paired x/y or just y with index
|
|
542
|
-
if x_cols and y_cols:
|
|
543
|
-
for x_col, y_col in zip(x_cols, y_cols):
|
|
544
|
-
x = csv_data[x_col].dropna().values
|
|
545
|
-
y = csv_data[y_col].dropna().values
|
|
546
|
-
if len(x) > 0 and len(y) > 0:
|
|
547
|
-
ax.plot(x[: len(y)], y[: len(x)])
|
|
548
|
-
elif y_cols:
|
|
549
|
-
for y_col in y_cols:
|
|
550
|
-
y = csv_data[y_col].dropna().values
|
|
551
|
-
if len(y) > 0:
|
|
552
|
-
ax.plot(y)
|
|
553
|
-
|
|
554
|
-
elif plot_type == "scatter":
|
|
555
|
-
if x_cols and y_cols:
|
|
556
|
-
x = csv_data[x_cols[0]].dropna().values
|
|
557
|
-
y = csv_data[y_cols[0]].dropna().values
|
|
558
|
-
ax.scatter(x[: min(len(x), len(y))], y[: min(len(x), len(y))])
|
|
559
|
-
|
|
560
|
-
# Add text annotations
|
|
561
|
-
text_cols = [c for c in columns if "text" in c.lower() and "content" in c.lower()]
|
|
562
|
-
for text_col in text_cols:
|
|
563
|
-
# Find corresponding x, y columns
|
|
564
|
-
prefix = text_col.rsplit("_content", 1)[0]
|
|
565
|
-
x_col = f"{prefix}_x"
|
|
566
|
-
y_col = f"{prefix}_y"
|
|
567
|
-
|
|
568
|
-
if x_col in columns and y_col in columns:
|
|
569
|
-
x_vals = csv_data[x_col].dropna()
|
|
570
|
-
y_vals = csv_data[y_col].dropna()
|
|
571
|
-
text_vals = csv_data[text_col].dropna()
|
|
572
|
-
|
|
573
|
-
for i in range(min(len(x_vals), len(y_vals), len(text_vals))):
|
|
574
|
-
ax.text(
|
|
575
|
-
x_vals.iloc[i],
|
|
576
|
-
y_vals.iloc[i],
|
|
577
|
-
str(text_vals.iloc[i]),
|
|
578
|
-
transform=ax.transAxes,
|
|
579
|
-
fontsize=6,
|
|
580
|
-
verticalalignment="top",
|
|
581
|
-
horizontalalignment="right",
|
|
582
|
-
)
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
def _apply_manual_overrides(fig, axes, overrides):
|
|
586
|
-
"""
|
|
587
|
-
Apply manual style/annotation overrides to figure.
|
|
588
|
-
|
|
589
|
-
Parameters
|
|
590
|
-
----------
|
|
591
|
-
fig : FigWrapper
|
|
592
|
-
Figure to modify
|
|
593
|
-
axes : AxisWrapper or array
|
|
594
|
-
Axes to modify
|
|
595
|
-
overrides : dict
|
|
596
|
-
Override specifications like {"axes[0].style.linewidth": 0.5}
|
|
597
|
-
"""
|
|
598
|
-
# Simple override application - can be extended
|
|
599
|
-
for key, value in overrides.items():
|
|
600
|
-
# Parse key like "axes[0].title" or "style.linewidth"
|
|
601
|
-
parts = key.split(".")
|
|
602
|
-
|
|
603
|
-
if parts[0].startswith("axes["):
|
|
604
|
-
# Extract axis index
|
|
605
|
-
import re
|
|
606
|
-
|
|
607
|
-
match = re.match(r"axes\[(\d+)\]", parts[0])
|
|
608
|
-
if match:
|
|
609
|
-
idx = int(match.group(1))
|
|
610
|
-
ax = axes if not hasattr(axes, "flat") else list(axes.flat)[idx]
|
|
611
|
-
|
|
612
|
-
if len(parts) > 1:
|
|
613
|
-
attr = parts[1]
|
|
614
|
-
if attr == "title":
|
|
615
|
-
ax.set_title(value)
|
|
616
|
-
elif attr == "xlabel":
|
|
617
|
-
ax.set_xlabel(value)
|
|
618
|
-
elif attr == "ylabel":
|
|
619
|
-
ax.set_ylabel(value)
|
|
322
|
+
fig_mpl = _plt.figure(*args, **kwargs)
|
|
323
|
+
return FigWrapper(fig_mpl)
|
|
620
324
|
|
|
621
325
|
|
|
622
326
|
def tight_layout(**kwargs):
|
|
@@ -627,9 +331,6 @@ def tight_layout(**kwargs):
|
|
|
627
331
|
1. UserWarning: "The figure layout has changed to tight" - informational only
|
|
628
332
|
2. RuntimeError: Colorbar layout incompatibility - occurs when colorbars exist with old engine
|
|
629
333
|
|
|
630
|
-
When a colorbar layout error occurs, the function silently continues as the layout
|
|
631
|
-
is still functional even if the engine cannot be changed.
|
|
632
|
-
|
|
633
334
|
Parameters
|
|
634
335
|
----------
|
|
635
336
|
**kwargs
|
|
@@ -637,18 +338,14 @@ def tight_layout(**kwargs):
|
|
|
637
338
|
"""
|
|
638
339
|
import warnings
|
|
639
340
|
|
|
640
|
-
import matplotlib.pyplot as plt
|
|
641
|
-
|
|
642
341
|
with warnings.catch_warnings():
|
|
643
342
|
warnings.filterwarnings(
|
|
644
343
|
"ignore", message="The figure layout has changed to tight"
|
|
645
344
|
)
|
|
646
345
|
try:
|
|
647
|
-
|
|
346
|
+
_plt.tight_layout(**kwargs)
|
|
648
347
|
except RuntimeError as e:
|
|
649
348
|
# Silently handle colorbar layout engine incompatibility
|
|
650
|
-
# This occurs when colorbars were created before tight_layout is called
|
|
651
|
-
# The layout is still usable, so we can safely ignore this error
|
|
652
349
|
if "Colorbar layout" not in str(e):
|
|
653
350
|
raise
|
|
654
351
|
|
|
@@ -664,12 +361,10 @@ def colorbar(mappable=None, cax=None, ax=None, **kwargs):
|
|
|
664
361
|
----------
|
|
665
362
|
mappable : ScalarMappable, optional
|
|
666
363
|
The image, contour set, etc. to which the colorbar applies.
|
|
667
|
-
If None, uses the current image.
|
|
668
364
|
cax : Axes, optional
|
|
669
365
|
Axes into which the colorbar will be drawn.
|
|
670
366
|
ax : Axes or AxisWrapper or list thereof, optional
|
|
671
367
|
Parent axes from which space for the colorbar will be stolen.
|
|
672
|
-
If None, uses current axes.
|
|
673
368
|
**kwargs
|
|
674
369
|
Additional keyword arguments passed to matplotlib.pyplot.colorbar()
|
|
675
370
|
|
|
@@ -678,8 +373,6 @@ def colorbar(mappable=None, cax=None, ax=None, **kwargs):
|
|
|
678
373
|
Colorbar
|
|
679
374
|
The created colorbar object
|
|
680
375
|
"""
|
|
681
|
-
import matplotlib.pyplot as plt
|
|
682
|
-
|
|
683
376
|
# Unwrap ax if it's a SciTeX AxisWrapper
|
|
684
377
|
if ax is not None:
|
|
685
378
|
if hasattr(ax, "__iter__") and not isinstance(ax, str):
|
|
@@ -694,7 +387,7 @@ def colorbar(mappable=None, cax=None, ax=None, **kwargs):
|
|
|
694
387
|
cax = cax._axis_mpl if hasattr(cax, "_axis_mpl") else cax
|
|
695
388
|
|
|
696
389
|
# Call matplotlib's colorbar with unwrapped axes
|
|
697
|
-
return
|
|
390
|
+
return _plt.colorbar(mappable=mappable, cax=cax, ax=ax, **kwargs)
|
|
698
391
|
|
|
699
392
|
|
|
700
393
|
def close(fig=None):
|
|
@@ -712,54 +405,72 @@ def close(fig=None):
|
|
|
712
405
|
- Figure or FigWrapper: close the specified figure
|
|
713
406
|
- int: close figure with that number
|
|
714
407
|
- str: close figure with that label, or 'all' to close all figures
|
|
715
|
-
|
|
716
|
-
Examples
|
|
717
|
-
--------
|
|
718
|
-
>>> import scitex.plt as splt
|
|
719
|
-
>>> fig, ax = splt.subplots()
|
|
720
|
-
>>> ax.plot([1, 2, 3])
|
|
721
|
-
>>> splt.close(fig) # Works with FigWrapper
|
|
722
|
-
|
|
723
|
-
>>> splt.close('all') # Close all figures
|
|
724
|
-
>>> splt.close() # Close current figure
|
|
725
|
-
|
|
726
|
-
See Also
|
|
727
|
-
--------
|
|
728
|
-
matplotlib.pyplot.close : Standard matplotlib close function
|
|
729
408
|
"""
|
|
730
|
-
import matplotlib.pyplot as plt
|
|
731
|
-
|
|
732
409
|
if fig is None:
|
|
733
|
-
|
|
734
|
-
plt.close()
|
|
410
|
+
_plt.close()
|
|
735
411
|
elif isinstance(fig, (int, str)):
|
|
736
|
-
|
|
737
|
-
plt.close(fig)
|
|
412
|
+
_plt.close(fig)
|
|
738
413
|
elif hasattr(fig, "_fig_mpl"):
|
|
739
414
|
# FigWrapper object - unwrap and close
|
|
740
|
-
|
|
415
|
+
_plt.close(fig._fig_mpl)
|
|
741
416
|
elif hasattr(fig, "figure"):
|
|
742
417
|
# Alternative attribute name (backward compatibility)
|
|
743
|
-
|
|
418
|
+
_plt.close(fig.figure)
|
|
419
|
+
elif hasattr(fig, "fig"):
|
|
420
|
+
# figrecipe RecordingFigure - unwrap and close
|
|
421
|
+
_plt.close(fig.fig)
|
|
744
422
|
else:
|
|
745
423
|
# Assume it's a matplotlib Figure
|
|
746
|
-
|
|
424
|
+
_plt.close(fig)
|
|
747
425
|
|
|
748
426
|
|
|
427
|
+
# ============================================================================
|
|
428
|
+
# Public API
|
|
429
|
+
# ============================================================================
|
|
430
|
+
|
|
749
431
|
__all__ = [
|
|
750
|
-
|
|
751
|
-
"
|
|
752
|
-
"
|
|
753
|
-
"
|
|
432
|
+
# Figrecipe core (re-exported with branding)
|
|
433
|
+
"subplots",
|
|
434
|
+
"save",
|
|
435
|
+
"reproduce",
|
|
436
|
+
"load", # Alias for reproduce
|
|
437
|
+
"crop",
|
|
438
|
+
"validate",
|
|
439
|
+
"extract_data",
|
|
440
|
+
"info",
|
|
754
441
|
"edit",
|
|
442
|
+
# Style management
|
|
443
|
+
"STYLE",
|
|
444
|
+
"load_style",
|
|
445
|
+
"unload_style",
|
|
446
|
+
"list_presets",
|
|
447
|
+
"apply_style",
|
|
448
|
+
# Composition
|
|
449
|
+
"compose",
|
|
450
|
+
"align_panels",
|
|
451
|
+
"distribute_panels",
|
|
452
|
+
"smart_align",
|
|
453
|
+
# Graph visualization
|
|
454
|
+
"draw_graph",
|
|
455
|
+
"get_graph_preset",
|
|
456
|
+
"list_graph_presets",
|
|
457
|
+
"register_graph_preset",
|
|
458
|
+
# Extensions
|
|
459
|
+
"sns",
|
|
460
|
+
"enable_svg",
|
|
461
|
+
# SciTeX-specific wrappers
|
|
755
462
|
"figure",
|
|
463
|
+
"colorbar",
|
|
464
|
+
"close",
|
|
465
|
+
"tight_layout",
|
|
466
|
+
# Local submodules
|
|
467
|
+
"ax",
|
|
468
|
+
"color",
|
|
756
469
|
"gallery",
|
|
757
|
-
"
|
|
470
|
+
"utils",
|
|
471
|
+
"styles",
|
|
758
472
|
"presets",
|
|
759
|
-
"subplots",
|
|
760
473
|
"termplot",
|
|
761
|
-
"tight_layout",
|
|
762
|
-
"utils",
|
|
763
474
|
]
|
|
764
475
|
|
|
765
476
|
|
|
@@ -768,35 +479,19 @@ def __getattr__(name):
|
|
|
768
479
|
Fallback to matplotlib.pyplot for any missing attributes.
|
|
769
480
|
This makes scitex.plt a complete drop-in replacement for matplotlib.pyplot.
|
|
770
481
|
"""
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
if hasattr(plt, name):
|
|
775
|
-
return getattr(plt, name)
|
|
776
|
-
else:
|
|
777
|
-
raise AttributeError(f"module 'scitex.plt' has no attribute '{name}'")
|
|
778
|
-
except ImportError:
|
|
779
|
-
raise AttributeError(
|
|
780
|
-
f"module 'scitex.plt' has no attribute '{name}' (matplotlib not available)"
|
|
781
|
-
)
|
|
482
|
+
if hasattr(_plt, name):
|
|
483
|
+
return getattr(_plt, name)
|
|
484
|
+
raise AttributeError(f"module 'scitex.plt' has no attribute '{name}'")
|
|
782
485
|
|
|
783
486
|
|
|
784
487
|
def __dir__():
|
|
785
488
|
"""
|
|
786
489
|
Provide comprehensive directory listing including matplotlib.pyplot functions.
|
|
787
490
|
"""
|
|
788
|
-
|
|
789
|
-
local_attrs = __all__.copy()
|
|
790
|
-
|
|
491
|
+
local_attrs = list(__all__)
|
|
791
492
|
# Add matplotlib.pyplot attributes
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
mpl_attrs = [attr for attr in dir(plt) if not attr.startswith("_")]
|
|
796
|
-
local_attrs.extend(mpl_attrs)
|
|
797
|
-
except ImportError:
|
|
798
|
-
pass
|
|
799
|
-
|
|
493
|
+
mpl_attrs = [attr for attr in dir(_plt) if not attr.startswith("_")]
|
|
494
|
+
local_attrs.extend(mpl_attrs)
|
|
800
495
|
return sorted(set(local_attrs))
|
|
801
496
|
|
|
802
497
|
|