figrecipe 0.6.0__py3-none-any.whl → 0.9.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.
- figrecipe/__init__.py +161 -1030
- figrecipe/__main__.py +12 -0
- figrecipe/_api/__init__.py +48 -0
- figrecipe/_api/_extract.py +108 -0
- figrecipe/_api/_notebook.py +61 -0
- figrecipe/_api/_panel.py +113 -0
- figrecipe/_api/_save.py +287 -0
- figrecipe/_api/_seaborn_proxy.py +34 -0
- figrecipe/_api/_style_manager.py +153 -0
- figrecipe/_api/_subplots.py +333 -0
- figrecipe/_api/_validate.py +82 -0
- figrecipe/_cli/__init__.py +7 -0
- figrecipe/_cli/_compose.py +87 -0
- figrecipe/_cli/_convert.py +117 -0
- figrecipe/_cli/_crop.py +82 -0
- figrecipe/_cli/_edit.py +70 -0
- figrecipe/_cli/_extract.py +128 -0
- figrecipe/_cli/_fonts.py +47 -0
- figrecipe/_cli/_info.py +67 -0
- figrecipe/_cli/_main.py +58 -0
- figrecipe/_cli/_reproduce.py +79 -0
- figrecipe/_cli/_style.py +77 -0
- figrecipe/_cli/_validate.py +66 -0
- figrecipe/_cli/_version.py +50 -0
- figrecipe/_composition/__init__.py +32 -0
- figrecipe/_composition/_alignment.py +452 -0
- figrecipe/_composition/_compose.py +179 -0
- figrecipe/_composition/_import_axes.py +127 -0
- figrecipe/_composition/_visibility.py +125 -0
- figrecipe/_dev/__init__.py +4 -93
- figrecipe/_dev/_plotters.py +76 -0
- figrecipe/_dev/_run_demos.py +56 -0
- figrecipe/_dev/browser/__init__.py +69 -0
- figrecipe/_dev/browser/_audio.py +240 -0
- figrecipe/_dev/browser/_caption.py +356 -0
- figrecipe/_dev/browser/_click_effect.py +146 -0
- figrecipe/_dev/browser/_cursor.py +196 -0
- figrecipe/_dev/browser/_highlight.py +105 -0
- figrecipe/_dev/browser/_narration.py +237 -0
- figrecipe/_dev/browser/_recorder.py +446 -0
- figrecipe/_dev/browser/_utils.py +178 -0
- figrecipe/_dev/browser/_video_trim/__init__.py +152 -0
- figrecipe/_dev/browser/_video_trim/_detection.py +223 -0
- figrecipe/_dev/browser/_video_trim/_markers.py +140 -0
- figrecipe/_dev/demo_plotters/__init__.py +35 -166
- figrecipe/_dev/demo_plotters/_categories.py +81 -0
- figrecipe/_dev/demo_plotters/_figure_creators.py +119 -0
- figrecipe/_dev/demo_plotters/_helpers.py +31 -0
- figrecipe/_dev/demo_plotters/_registry.py +50 -0
- figrecipe/_dev/demo_plotters/bar_categorical/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/{plot_plot.py → line_curve/plot_plot.py} +3 -2
- figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/{plot_pie.py → special/plot_pie.py} +5 -1
- figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
- figrecipe/_editor/__init__.py +61 -13
- figrecipe/_editor/_bbox/__init__.py +43 -0
- figrecipe/_editor/_bbox/_collections.py +177 -0
- figrecipe/_editor/_bbox/_elements.py +159 -0
- figrecipe/_editor/_bbox/_extract.py +402 -0
- figrecipe/_editor/_bbox/_extract_axes.py +370 -0
- figrecipe/_editor/_bbox/_extract_text.py +466 -0
- figrecipe/_editor/_bbox/_lines.py +173 -0
- figrecipe/_editor/_bbox/_transforms.py +146 -0
- figrecipe/_editor/_call_overrides.py +183 -0
- figrecipe/_editor/_datatable_plot_handlers.py +249 -0
- figrecipe/_editor/_figure_layout.py +211 -0
- figrecipe/_editor/_flask_app.py +200 -1030
- figrecipe/_editor/_helpers.py +251 -0
- figrecipe/_editor/_hitmap/__init__.py +76 -0
- figrecipe/_editor/_hitmap/_artists/__init__.py +21 -0
- figrecipe/_editor/_hitmap/_artists/_collections.py +345 -0
- figrecipe/_editor/_hitmap/_artists/_images.py +68 -0
- figrecipe/_editor/_hitmap/_artists/_lines.py +107 -0
- figrecipe/_editor/_hitmap/_artists/_patches.py +163 -0
- figrecipe/_editor/_hitmap/_artists/_text.py +190 -0
- figrecipe/_editor/_hitmap/_colors.py +181 -0
- figrecipe/_editor/_hitmap/_detect.py +194 -0
- figrecipe/_editor/_hitmap/_restore.py +154 -0
- figrecipe/_editor/_hitmap_main.py +182 -0
- figrecipe/_editor/_overrides.py +4 -1
- figrecipe/_editor/_plot_types_registry.py +190 -0
- figrecipe/_editor/_preferences.py +135 -0
- figrecipe/_editor/_render_overrides.py +507 -0
- figrecipe/_editor/_renderer.py +81 -186
- figrecipe/_editor/_routes_annotation.py +114 -0
- figrecipe/_editor/_routes_axis.py +482 -0
- figrecipe/_editor/_routes_captions.py +130 -0
- figrecipe/_editor/_routes_composition.py +270 -0
- figrecipe/_editor/_routes_core.py +126 -0
- figrecipe/_editor/_routes_datatable.py +364 -0
- figrecipe/_editor/_routes_element.py +335 -0
- figrecipe/_editor/_routes_files.py +443 -0
- figrecipe/_editor/_routes_image.py +200 -0
- figrecipe/_editor/_routes_snapshot.py +94 -0
- figrecipe/_editor/_routes_style.py +243 -0
- figrecipe/_editor/_templates/__init__.py +116 -1
- figrecipe/_editor/_templates/_html.py +154 -64
- figrecipe/_editor/_templates/_html_components/__init__.py +13 -0
- figrecipe/_editor/_templates/_html_components/_composition_toolbar.py +79 -0
- figrecipe/_editor/_templates/_html_components/_file_browser.py +41 -0
- figrecipe/_editor/_templates/_html_datatable.py +92 -0
- figrecipe/_editor/_templates/_scripts/__init__.py +178 -0
- figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
- figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
- figrecipe/_editor/_templates/_scripts/_api.py +228 -0
- figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
- figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
- figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
- figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
- figrecipe/_editor/_templates/_scripts/_core.py +493 -0
- figrecipe/_editor/_templates/_scripts/_datatable/__init__.py +59 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_cell_edit.py +97 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_clipboard.py +164 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_context_menu.py +221 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_core.py +150 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_editable.py +511 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_import.py +161 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_plot.py +261 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_selection.py +438 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_table.py +256 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_tabs.py +354 -0
- figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
- figrecipe/_editor/_templates/_scripts/_element_editor.py +325 -0
- figrecipe/_editor/_templates/_scripts/_files.py +429 -0
- figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
- figrecipe/_editor/_templates/_scripts/_hitmap.py +512 -0
- figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
- figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
- figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
- figrecipe/_editor/_templates/_scripts/_legend_drag.py +270 -0
- figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
- figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
- figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag.py +505 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
- figrecipe/_editor/_templates/_scripts/_panel_position.py +463 -0
- figrecipe/_editor/_templates/_scripts/_panel_resize.py +230 -0
- figrecipe/_editor/_templates/_scripts/_panel_snap.py +307 -0
- figrecipe/_editor/_templates/_scripts/_region_select.py +255 -0
- figrecipe/_editor/_templates/_scripts/_selection.py +244 -0
- figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
- figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
- figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
- figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
- figrecipe/_editor/_templates/_scripts/_zoom.py +212 -0
- figrecipe/_editor/_templates/_styles/__init__.py +78 -0
- figrecipe/_editor/_templates/_styles/_base.py +111 -0
- figrecipe/_editor/_templates/_styles/_buttons.py +327 -0
- figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
- figrecipe/_editor/_templates/_styles/_composition.py +87 -0
- figrecipe/_editor/_templates/_styles/_controls.py +430 -0
- figrecipe/_editor/_templates/_styles/_datatable/__init__.py +40 -0
- figrecipe/_editor/_templates/_styles/_datatable/_editable.py +203 -0
- figrecipe/_editor/_templates/_styles/_datatable/_panel.py +268 -0
- figrecipe/_editor/_templates/_styles/_datatable/_table.py +479 -0
- figrecipe/_editor/_templates/_styles/_datatable/_toolbar.py +384 -0
- figrecipe/_editor/_templates/_styles/_datatable/_vars.py +123 -0
- figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
- figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
- figrecipe/_editor/_templates/_styles/_forms.py +224 -0
- figrecipe/_editor/_templates/_styles/_hitmap.py +191 -0
- figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
- figrecipe/_editor/_templates/_styles/_labels.py +118 -0
- figrecipe/_editor/_templates/_styles/_modals.py +127 -0
- figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
- figrecipe/_editor/_templates/_styles/_preview.py +430 -0
- figrecipe/_editor/_templates/_styles/_selection.py +73 -0
- figrecipe/_editor/_templates/_styles/_spinner.py +117 -0
- figrecipe/_editor/static/audio/click.mp3 +0 -0
- figrecipe/_editor/static/click.mp3 +0 -0
- figrecipe/_editor/static/icons/favicon.ico +0 -0
- figrecipe/_integrations/__init__.py +17 -0
- figrecipe/_integrations/_scitex_stats.py +298 -0
- figrecipe/_params/_DECORATION_METHODS.py +8 -0
- figrecipe/_recorder.py +63 -109
- figrecipe/_recorder_utils.py +124 -0
- figrecipe/_reproducer/__init__.py +18 -0
- figrecipe/_reproducer/_core.py +509 -0
- figrecipe/_reproducer/_custom_plots.py +279 -0
- figrecipe/_reproducer/_seaborn.py +100 -0
- figrecipe/_reproducer/_violin.py +186 -0
- figrecipe/_signatures/_kwargs.py +273 -0
- figrecipe/_signatures/_loader.py +21 -423
- figrecipe/_signatures/_parsing.py +147 -0
- figrecipe/_utils/__init__.py +3 -0
- figrecipe/_utils/_bundle.py +205 -0
- figrecipe/_wrappers/_axes.py +252 -895
- figrecipe/_wrappers/_axes_helpers.py +136 -0
- figrecipe/_wrappers/_axes_plots.py +418 -0
- figrecipe/_wrappers/_axes_seaborn.py +157 -0
- figrecipe/_wrappers/_caption_generator.py +218 -0
- figrecipe/_wrappers/_figure.py +188 -1
- figrecipe/_wrappers/_panel_labels.py +127 -0
- figrecipe/_wrappers/_plot_helpers.py +143 -0
- figrecipe/_wrappers/_stat_annotation.py +274 -0
- figrecipe/_wrappers/_violin_helpers.py +180 -0
- figrecipe/styles/__init__.py +8 -6
- figrecipe/styles/_dotdict.py +72 -0
- figrecipe/styles/_finalize.py +134 -0
- figrecipe/styles/_fonts.py +77 -0
- figrecipe/styles/_kwargs_converter.py +178 -0
- figrecipe/styles/_plot_styles.py +209 -0
- figrecipe/styles/_style_applier.py +42 -480
- figrecipe/styles/_style_loader.py +16 -192
- figrecipe/styles/_themes.py +151 -0
- figrecipe/styles/presets/MATPLOTLIB.yaml +2 -1
- figrecipe/styles/presets/SCITEX.yaml +40 -28
- figrecipe-0.9.0.dist-info/METADATA +427 -0
- figrecipe-0.9.0.dist-info/RECORD +277 -0
- figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
- figrecipe/_editor/_bbox.py +0 -978
- figrecipe/_editor/_hitmap.py +0 -937
- figrecipe/_editor/_templates/_scripts.py +0 -2778
- figrecipe/_editor/_templates/_styles.py +0 -1326
- figrecipe/_reproducer.py +0 -975
- figrecipe-0.6.0.dist-info/METADATA +0 -394
- figrecipe-0.6.0.dist-info/RECORD +0 -90
- /figrecipe/_dev/demo_plotters/{plot_bar.py → bar_categorical/plot_bar.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_barh.py → bar_categorical/plot_barh.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_contour.py → contour_surface/plot_contour.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_contourf.py → contour_surface/plot_contourf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tricontour.py → contour_surface/plot_tricontour.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tricontourf.py → contour_surface/plot_tricontourf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tripcolor.py → contour_surface/plot_tripcolor.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_triplot.py → contour_surface/plot_triplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_boxplot.py → distribution/plot_boxplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_ecdf.py → distribution/plot_ecdf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hist.py → distribution/plot_hist.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hist2d.py → distribution/plot_hist2d.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_violinplot.py → distribution/plot_violinplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hexbin.py → image_matrix/plot_hexbin.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_imshow.py → image_matrix/plot_imshow.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_matshow.py → image_matrix/plot_matshow.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_pcolor.py → image_matrix/plot_pcolor.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_pcolormesh.py → image_matrix/plot_pcolormesh.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_spy.py → image_matrix/plot_spy.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_errorbar.py → line_curve/plot_errorbar.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill.py → line_curve/plot_fill.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill_between.py → line_curve/plot_fill_between.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill_betweenx.py → line_curve/plot_fill_betweenx.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stackplot.py → line_curve/plot_stackplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stairs.py → line_curve/plot_stairs.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_step.py → line_curve/plot_step.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_scatter.py → scatter_points/plot_scatter.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_eventplot.py → special/plot_eventplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_loglog.py → special/plot_loglog.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_semilogx.py → special/plot_semilogx.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_semilogy.py → special/plot_semilogy.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stem.py → special/plot_stem.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_acorr.py → spectral_signal/plot_acorr.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_angle_spectrum.py → spectral_signal/plot_angle_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_cohere.py → spectral_signal/plot_cohere.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_csd.py → spectral_signal/plot_csd.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_magnitude_spectrum.py → spectral_signal/plot_magnitude_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_phase_spectrum.py → spectral_signal/plot_phase_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_psd.py → spectral_signal/plot_psd.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_specgram.py → spectral_signal/plot_specgram.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_xcorr.py → spectral_signal/plot_xcorr.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_barbs.py → vector_flow/plot_barbs.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_quiver.py → vector_flow/plot_quiver.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_streamplot.py → vector_flow/plot_streamplot.py} +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -10,206 +10,26 @@ __all__ = [
|
|
|
10
10
|
"apply_theme_colors",
|
|
11
11
|
"check_font",
|
|
12
12
|
"finalize_ticks",
|
|
13
|
+
"finalize_special_plots",
|
|
13
14
|
"list_available_fonts",
|
|
14
15
|
]
|
|
15
16
|
|
|
16
|
-
import
|
|
17
|
-
from typing import Any, Dict, List, Optional
|
|
17
|
+
from typing import Any, Dict
|
|
18
18
|
|
|
19
19
|
from matplotlib.axes import Axes
|
|
20
20
|
|
|
21
21
|
from .._utils._units import mm_to_pt
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
--------
|
|
34
|
-
>>> fonts = ps.list_available_fonts()
|
|
35
|
-
>>> print(fonts[:5])
|
|
36
|
-
['Arial', 'Courier New', 'DejaVu Sans', ...]
|
|
37
|
-
"""
|
|
38
|
-
import matplotlib.font_manager as fm
|
|
39
|
-
|
|
40
|
-
fonts = set()
|
|
41
|
-
for font in fm.fontManager.ttflist:
|
|
42
|
-
fonts.add(font.name)
|
|
43
|
-
return sorted(fonts)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def check_font(font_family: str, fallback: str = "DejaVu Sans") -> str:
|
|
47
|
-
"""Check if font is available, with fallback and helpful error message.
|
|
48
|
-
|
|
49
|
-
Parameters
|
|
50
|
-
----------
|
|
51
|
-
font_family : str
|
|
52
|
-
Requested font family name.
|
|
53
|
-
fallback : str
|
|
54
|
-
Fallback font if requested font is not available.
|
|
55
|
-
|
|
56
|
-
Returns
|
|
57
|
-
-------
|
|
58
|
-
str
|
|
59
|
-
The font to use (original if available, fallback otherwise).
|
|
60
|
-
|
|
61
|
-
Examples
|
|
62
|
-
--------
|
|
63
|
-
>>> font = check_font("Arial") # Returns "Arial" if available
|
|
64
|
-
>>> font = check_font("NonExistentFont") # Returns fallback with warning
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
available = list_available_fonts()
|
|
68
|
-
|
|
69
|
-
if font_family in available:
|
|
70
|
-
return font_family
|
|
71
|
-
|
|
72
|
-
# Font not found - show helpful message
|
|
73
|
-
similar = [f for f in available if font_family.lower() in f.lower()]
|
|
74
|
-
|
|
75
|
-
msg = f"Font '{font_family}' not found.\n"
|
|
76
|
-
if similar:
|
|
77
|
-
msg += f" Similar fonts available: {similar[:5]}\n"
|
|
78
|
-
msg += f" Using fallback: '{fallback}'\n"
|
|
79
|
-
msg += " To see all available fonts: ps.list_available_fonts()\n"
|
|
80
|
-
msg += " To install Arial on Linux: sudo apt install ttf-mscorefonts-installer"
|
|
81
|
-
|
|
82
|
-
warnings.warn(msg, UserWarning)
|
|
83
|
-
|
|
84
|
-
return fallback if fallback in available else "DejaVu Sans"
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
# Default theme color palettes (Monaco/VS Code style for dark)
|
|
88
|
-
THEME_COLORS = {
|
|
89
|
-
"dark": {
|
|
90
|
-
"figure_bg": "#1e1e1e", # VS Code main background
|
|
91
|
-
"axes_bg": "#252526", # VS Code panel background
|
|
92
|
-
"legend_bg": "#252526", # Same as axes
|
|
93
|
-
"text": "#d4d4d4", # VS Code default text
|
|
94
|
-
"spine": "#3c3c3c", # Subtle border color
|
|
95
|
-
"tick": "#d4d4d4", # Match text
|
|
96
|
-
"grid": "#3a3a3a", # Subtle grid
|
|
97
|
-
},
|
|
98
|
-
"light": {
|
|
99
|
-
"figure_bg": "none", # Transparent
|
|
100
|
-
"axes_bg": "none", # Transparent
|
|
101
|
-
"legend_bg": "none", # Transparent
|
|
102
|
-
"text": "black",
|
|
103
|
-
"spine": "black",
|
|
104
|
-
"tick": "black",
|
|
105
|
-
"grid": "#cccccc",
|
|
106
|
-
},
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def apply_theme_colors(
|
|
111
|
-
ax: Axes,
|
|
112
|
-
theme: str = "light",
|
|
113
|
-
custom_colors: Optional[Dict[str, str]] = None,
|
|
114
|
-
) -> None:
|
|
115
|
-
"""Apply theme colors to axes for dark/light mode support.
|
|
116
|
-
|
|
117
|
-
Parameters
|
|
118
|
-
----------
|
|
119
|
-
ax : matplotlib.axes.Axes
|
|
120
|
-
Target axes to apply theme to
|
|
121
|
-
theme : str or dict
|
|
122
|
-
Color theme: "light" or "dark" (default: "light")
|
|
123
|
-
If dict, extracts 'mode' key (for YAML-style theme dicts)
|
|
124
|
-
custom_colors : dict, optional
|
|
125
|
-
Custom color overrides. Keys: figure_bg, axes_bg, legend_bg, text, spine, tick, grid
|
|
126
|
-
|
|
127
|
-
Examples
|
|
128
|
-
--------
|
|
129
|
-
>>> fig, ax = plt.subplots()
|
|
130
|
-
>>> apply_theme_colors(ax, theme="dark") # Eye-friendly dark mode
|
|
131
|
-
"""
|
|
132
|
-
# Handle dict-style theme (from YAML: {mode: "light", dark: {...}})
|
|
133
|
-
if isinstance(theme, dict):
|
|
134
|
-
theme = theme.get("mode", "light")
|
|
135
|
-
|
|
136
|
-
# Ensure theme is a string
|
|
137
|
-
if not isinstance(theme, str):
|
|
138
|
-
theme = "light"
|
|
139
|
-
|
|
140
|
-
# Get base theme colors
|
|
141
|
-
colors = THEME_COLORS.get(theme, THEME_COLORS["light"]).copy()
|
|
142
|
-
|
|
143
|
-
# Apply custom overrides
|
|
144
|
-
if custom_colors:
|
|
145
|
-
# Handle legacy key name (background -> figure_bg)
|
|
146
|
-
if "background" in custom_colors and "figure_bg" not in custom_colors:
|
|
147
|
-
custom_colors["figure_bg"] = custom_colors.pop("background")
|
|
148
|
-
colors.update(custom_colors)
|
|
149
|
-
|
|
150
|
-
# Helper to check for transparent/none
|
|
151
|
-
def is_transparent(color):
|
|
152
|
-
if color is None:
|
|
153
|
-
return False
|
|
154
|
-
return str(color).lower() in ("none", "transparent")
|
|
155
|
-
|
|
156
|
-
# Apply axes background (handle "none"/"transparent" for transparency)
|
|
157
|
-
axes_bg = colors.get("axes_bg", "none")
|
|
158
|
-
if is_transparent(axes_bg):
|
|
159
|
-
ax.set_facecolor("none")
|
|
160
|
-
ax.patch.set_alpha(0)
|
|
161
|
-
else:
|
|
162
|
-
ax.set_facecolor(axes_bg)
|
|
163
|
-
|
|
164
|
-
# Apply figure background if accessible
|
|
165
|
-
fig = ax.get_figure()
|
|
166
|
-
if fig is not None:
|
|
167
|
-
fig_bg = colors.get("figure_bg", "none")
|
|
168
|
-
if is_transparent(fig_bg):
|
|
169
|
-
fig.patch.set_facecolor("none")
|
|
170
|
-
fig.patch.set_alpha(0)
|
|
171
|
-
else:
|
|
172
|
-
fig.patch.set_facecolor(fig_bg)
|
|
173
|
-
|
|
174
|
-
# Apply text colors to figure-level text elements (suptitle, supxlabel, supylabel)
|
|
175
|
-
if hasattr(fig, "_suptitle") and fig._suptitle is not None:
|
|
176
|
-
fig._suptitle.set_color(colors["text"])
|
|
177
|
-
if hasattr(fig, "_supxlabel") and fig._supxlabel is not None:
|
|
178
|
-
fig._supxlabel.set_color(colors["text"])
|
|
179
|
-
if hasattr(fig, "_supylabel") and fig._supylabel is not None:
|
|
180
|
-
fig._supylabel.set_color(colors["text"])
|
|
181
|
-
|
|
182
|
-
# Apply text colors (labels, titles)
|
|
183
|
-
ax.xaxis.label.set_color(colors["text"])
|
|
184
|
-
ax.yaxis.label.set_color(colors["text"])
|
|
185
|
-
ax.title.set_color(colors["text"])
|
|
186
|
-
|
|
187
|
-
# Apply spine colors
|
|
188
|
-
for spine in ax.spines.values():
|
|
189
|
-
spine.set_color(colors["spine"])
|
|
190
|
-
|
|
191
|
-
# Apply tick colors (both marks and labels)
|
|
192
|
-
ax.tick_params(colors=colors["tick"], which="both")
|
|
193
|
-
for label in ax.get_xticklabels() + ax.get_yticklabels():
|
|
194
|
-
label.set_color(colors["tick"])
|
|
195
|
-
|
|
196
|
-
# Apply legend colors if legend exists
|
|
197
|
-
legend = ax.get_legend()
|
|
198
|
-
if legend is not None:
|
|
199
|
-
for text in legend.get_texts():
|
|
200
|
-
text.set_color(colors["text"])
|
|
201
|
-
title = legend.get_title()
|
|
202
|
-
if title:
|
|
203
|
-
title.set_color(colors["text"])
|
|
204
|
-
frame = legend.get_frame()
|
|
205
|
-
if frame:
|
|
206
|
-
legend_bg = colors.get("legend_bg", colors.get("axes_bg", "none"))
|
|
207
|
-
if is_transparent(legend_bg):
|
|
208
|
-
frame.set_facecolor("none")
|
|
209
|
-
frame.set_alpha(0)
|
|
210
|
-
else:
|
|
211
|
-
frame.set_facecolor(legend_bg)
|
|
212
|
-
frame.set_edgecolor(colors["spine"])
|
|
22
|
+
from ._finalize import finalize_special_plots, finalize_ticks
|
|
23
|
+
from ._fonts import check_font, list_available_fonts
|
|
24
|
+
from ._plot_styles import (
|
|
25
|
+
apply_barplot_style,
|
|
26
|
+
apply_boxplot_style,
|
|
27
|
+
apply_histogram_style,
|
|
28
|
+
apply_matrix_style,
|
|
29
|
+
apply_pie_style,
|
|
30
|
+
apply_violinplot_style,
|
|
31
|
+
)
|
|
32
|
+
from ._themes import THEME_COLORS, apply_theme_colors
|
|
213
33
|
|
|
214
34
|
|
|
215
35
|
def apply_style_mm(ax: Axes, style: Dict[str, Any]) -> float:
|
|
@@ -262,8 +82,14 @@ def apply_style_mm(ax: Axes, style: Dict[str, Any]) -> float:
|
|
|
262
82
|
>>> trace_lw = apply_style_mm(ax, style)
|
|
263
83
|
>>> ax.plot(x, y, lw=trace_lw)
|
|
264
84
|
"""
|
|
85
|
+
import matplotlib as mpl
|
|
86
|
+
|
|
265
87
|
# Apply theme colors (dark/light mode)
|
|
266
|
-
|
|
88
|
+
theme_section = style.get("theme", {})
|
|
89
|
+
if isinstance(theme_section, dict):
|
|
90
|
+
theme = theme_section.get("mode", "light")
|
|
91
|
+
else:
|
|
92
|
+
theme = str(theme_section) if theme_section else "light"
|
|
267
93
|
theme_colors = style.get("theme_colors", None)
|
|
268
94
|
apply_theme_colors(ax, theme, theme_colors)
|
|
269
95
|
|
|
@@ -284,43 +110,27 @@ def apply_style_mm(ax: Axes, style: Dict[str, Any]) -> float:
|
|
|
284
110
|
# Convert marker size from mm to points
|
|
285
111
|
marker_size_mm = style.get("marker_size_mm")
|
|
286
112
|
if marker_size_mm is not None:
|
|
287
|
-
import matplotlib as mpl
|
|
288
|
-
|
|
289
113
|
marker_size_pt = mm_to_pt(marker_size_mm)
|
|
290
114
|
mpl.rcParams["lines.markersize"] = marker_size_pt
|
|
291
115
|
|
|
292
116
|
# Set boxplot flier (outlier) marker size
|
|
293
117
|
flier_mm = style.get("markers_flier_mm", style.get("flier_mm"))
|
|
294
118
|
if flier_mm is not None:
|
|
295
|
-
import matplotlib as mpl
|
|
296
|
-
|
|
297
119
|
flier_size_pt = mm_to_pt(flier_mm)
|
|
298
120
|
mpl.rcParams["boxplot.flierprops.markersize"] = flier_size_pt
|
|
299
121
|
|
|
300
122
|
# Set boxplot median color
|
|
301
123
|
median_color = style.get("boxplot_median_color")
|
|
302
124
|
if median_color is not None:
|
|
303
|
-
import matplotlib as mpl
|
|
304
|
-
|
|
305
125
|
mpl.rcParams["boxplot.medianprops.color"] = median_color
|
|
306
126
|
|
|
307
|
-
# Apply
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
_apply_barplot_style(ax, style)
|
|
315
|
-
|
|
316
|
-
# Apply histogram edge widths to existing histogram elements
|
|
317
|
-
_apply_histogram_style(ax, style)
|
|
318
|
-
|
|
319
|
-
# Apply pie chart styling
|
|
320
|
-
_apply_pie_style(ax, style)
|
|
321
|
-
|
|
322
|
-
# Apply imshow/matshow/spy styling (hide axes if configured)
|
|
323
|
-
_apply_matrix_style(ax, style)
|
|
127
|
+
# Apply plot-specific styles
|
|
128
|
+
apply_boxplot_style(ax, style)
|
|
129
|
+
apply_violinplot_style(ax, style)
|
|
130
|
+
apply_barplot_style(ax, style)
|
|
131
|
+
apply_histogram_style(ax, style)
|
|
132
|
+
apply_pie_style(ax, style)
|
|
133
|
+
apply_matrix_style(ax, style)
|
|
324
134
|
|
|
325
135
|
# Configure tick parameters
|
|
326
136
|
tick_pad_pt = style.get("tick_pad_pt", 2.0)
|
|
@@ -334,7 +144,7 @@ def apply_style_mm(ax: Axes, style: Dict[str, Any]) -> float:
|
|
|
334
144
|
right=False,
|
|
335
145
|
)
|
|
336
146
|
|
|
337
|
-
# Apply font sizes and family
|
|
147
|
+
# Apply font sizes and family
|
|
338
148
|
axis_fs = style.get("axis_font_size_pt", 8)
|
|
339
149
|
tick_fs = style.get("tick_font_size_pt", 7)
|
|
340
150
|
title_fs = style.get("title_font_size_pt", 9)
|
|
@@ -360,14 +170,16 @@ def apply_style_mm(ax: Axes, style: Dict[str, Any]) -> float:
|
|
|
360
170
|
title_pad_pt = style.get("title_pad_pt", 4.0)
|
|
361
171
|
ax.set_title(ax.get_title(), pad=title_pad_pt)
|
|
362
172
|
|
|
363
|
-
# Set legend font size and background via rcParams
|
|
364
|
-
import matplotlib as mpl
|
|
365
|
-
|
|
173
|
+
# Set legend font size and background via rcParams
|
|
366
174
|
mpl.rcParams["legend.fontsize"] = legend_fs
|
|
367
175
|
mpl.rcParams["legend.title_fontsize"] = legend_fs
|
|
368
176
|
|
|
369
177
|
# Set legend colors from theme
|
|
370
|
-
|
|
178
|
+
theme_section = style.get("theme", {})
|
|
179
|
+
if isinstance(theme_section, dict):
|
|
180
|
+
theme = theme_section.get("mode", "light")
|
|
181
|
+
else:
|
|
182
|
+
theme = str(theme_section) if theme_section else "light"
|
|
371
183
|
theme_colors = style.get("theme_colors", None)
|
|
372
184
|
if theme_colors:
|
|
373
185
|
legend_bg = theme_colors.get("legend_bg", theme_colors.get("axes_bg", "white"))
|
|
@@ -403,34 +215,26 @@ def apply_style_mm(ax: Axes, style: Dict[str, Any]) -> float:
|
|
|
403
215
|
else:
|
|
404
216
|
ax.grid(False)
|
|
405
217
|
|
|
406
|
-
# Configure number of ticks (
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
# This will be applied in _finalize_ticks() before saving
|
|
413
|
-
ax._figrecipe_n_ticks = n_ticks
|
|
218
|
+
# Configure number of ticks (deferred to finalize_ticks)
|
|
219
|
+
n_ticks_min = style.get("n_ticks_min")
|
|
220
|
+
n_ticks_max = style.get("n_ticks_max")
|
|
221
|
+
if n_ticks_min is not None or n_ticks_max is not None:
|
|
222
|
+
ax._figrecipe_n_ticks_min = n_ticks_min or 3
|
|
223
|
+
ax._figrecipe_n_ticks_max = n_ticks_max or 4
|
|
414
224
|
|
|
415
225
|
# Apply color palette to both rcParams and this specific axes
|
|
416
226
|
color_palette = style.get("color_palette")
|
|
417
227
|
if color_palette is not None:
|
|
418
|
-
import matplotlib as mpl
|
|
419
|
-
|
|
420
|
-
# Normalize colors (RGB 0-255 to 0-1)
|
|
421
228
|
normalized_palette = []
|
|
422
229
|
for c in color_palette:
|
|
423
230
|
if isinstance(c, (list, tuple)) and len(c) >= 3:
|
|
424
|
-
# Check if already normalized
|
|
425
231
|
if all(v <= 1.0 for v in c):
|
|
426
232
|
normalized_palette.append(tuple(c))
|
|
427
233
|
else:
|
|
428
234
|
normalized_palette.append(tuple(v / 255.0 for v in c))
|
|
429
235
|
else:
|
|
430
236
|
normalized_palette.append(c)
|
|
431
|
-
# Set rcParams for future axes
|
|
432
237
|
mpl.rcParams["axes.prop_cycle"] = mpl.cycler(color=normalized_palette)
|
|
433
|
-
# Also set the color cycle on this specific axes (axes cache cycler at creation)
|
|
434
238
|
ax.set_prop_cycle(color=normalized_palette)
|
|
435
239
|
|
|
436
240
|
# Store style in axes for reference
|
|
@@ -441,254 +245,10 @@ def apply_style_mm(ax: Axes, style: Dict[str, Any]) -> float:
|
|
|
441
245
|
return trace_lw_pt
|
|
442
246
|
|
|
443
247
|
|
|
444
|
-
def _apply_boxplot_style(ax: Axes, style: Dict[str, Any]) -> None:
|
|
445
|
-
"""Apply boxplot line width styling to existing boxplot elements.
|
|
446
|
-
|
|
447
|
-
Parameters
|
|
448
|
-
----------
|
|
449
|
-
ax : matplotlib.axes.Axes
|
|
450
|
-
Target axes containing boxplot elements.
|
|
451
|
-
style : dict
|
|
452
|
-
Style dictionary with boxplot_* keys.
|
|
453
|
-
"""
|
|
454
|
-
from matplotlib.lines import Line2D
|
|
455
|
-
from matplotlib.patches import PathPatch
|
|
456
|
-
|
|
457
|
-
# Get line widths from style
|
|
458
|
-
box_lw = mm_to_pt(style.get("boxplot_line_mm", 0.2))
|
|
459
|
-
whisker_lw = mm_to_pt(style.get("boxplot_whisker_mm", 0.2))
|
|
460
|
-
cap_lw = mm_to_pt(style.get("boxplot_cap_mm", 0.2))
|
|
461
|
-
median_lw = mm_to_pt(style.get("boxplot_median_mm", 0.2))
|
|
462
|
-
median_color = style.get("boxplot_median_color", "black")
|
|
463
|
-
flier_edge_lw = mm_to_pt(style.get("boxplot_flier_edge_mm", 0.2))
|
|
464
|
-
|
|
465
|
-
# Boxplot creates Line2D objects for whiskers, caps, medians, fliers
|
|
466
|
-
# and PathPatch objects for boxes
|
|
467
|
-
for child in ax.get_children():
|
|
468
|
-
# Check if it's a boxplot box (PathPatch with specific properties)
|
|
469
|
-
if isinstance(child, PathPatch):
|
|
470
|
-
# Boxes are typically PathPatch with edgecolor
|
|
471
|
-
if child.get_edgecolor() is not None:
|
|
472
|
-
child.set_linewidth(box_lw)
|
|
473
|
-
|
|
474
|
-
# Check for Line2D objects (whiskers, caps, medians, fliers)
|
|
475
|
-
elif isinstance(child, Line2D):
|
|
476
|
-
xdata = child.get_xdata()
|
|
477
|
-
ydata = child.get_ydata()
|
|
478
|
-
|
|
479
|
-
# Fliers are markers with no line (linestyle='None' or '')
|
|
480
|
-
# and typically have varying number of points (outliers)
|
|
481
|
-
marker = child.get_marker()
|
|
482
|
-
linestyle = child.get_linestyle()
|
|
483
|
-
if marker and marker != "None" and linestyle in ("None", "", " "):
|
|
484
|
-
# This is likely a flier (outlier marker)
|
|
485
|
-
child.set_markeredgewidth(flier_edge_lw)
|
|
486
|
-
elif len(xdata) == 2 and len(ydata) == 2:
|
|
487
|
-
# Horizontal line (could be median or cap)
|
|
488
|
-
if ydata[0] == ydata[1]:
|
|
489
|
-
# Check if it's likely a median (middle of box) or cap
|
|
490
|
-
# Medians are usually solid, caps are at extremes
|
|
491
|
-
if linestyle == "-":
|
|
492
|
-
# Could be median - apply median style
|
|
493
|
-
child.set_linewidth(median_lw)
|
|
494
|
-
if median_color:
|
|
495
|
-
child.set_color(median_color)
|
|
496
|
-
else:
|
|
497
|
-
child.set_linewidth(cap_lw)
|
|
498
|
-
# Vertical line (whisker)
|
|
499
|
-
elif xdata[0] == xdata[1]:
|
|
500
|
-
child.set_linewidth(whisker_lw)
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
def _apply_violinplot_style(ax: Axes, style: Dict[str, Any]) -> None:
|
|
504
|
-
"""Apply violinplot line width styling to existing violinplot elements.
|
|
505
|
-
|
|
506
|
-
Parameters
|
|
507
|
-
----------
|
|
508
|
-
ax : matplotlib.axes.Axes
|
|
509
|
-
Target axes containing violinplot elements.
|
|
510
|
-
style : dict
|
|
511
|
-
Style dictionary with violinplot_* keys.
|
|
512
|
-
"""
|
|
513
|
-
from matplotlib.collections import LineCollection, PolyCollection
|
|
514
|
-
|
|
515
|
-
# Get line widths from style
|
|
516
|
-
body_lw = mm_to_pt(style.get("violinplot_line_mm", 0.2))
|
|
517
|
-
whisker_lw = mm_to_pt(style.get("violinplot_whisker_mm", 0.2))
|
|
518
|
-
|
|
519
|
-
for child in ax.get_children():
|
|
520
|
-
# Violin bodies are PolyCollection
|
|
521
|
-
if isinstance(child, PolyCollection):
|
|
522
|
-
child.set_linewidth(body_lw)
|
|
523
|
-
|
|
524
|
-
# Violin inner elements (cbars, cmins, cmaxes) are LineCollection
|
|
525
|
-
elif isinstance(child, LineCollection):
|
|
526
|
-
child.set_linewidth(whisker_lw)
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
def _apply_barplot_style(ax: Axes, style: Dict[str, Any]) -> None:
|
|
530
|
-
"""Apply barplot edge styling to existing bar elements.
|
|
531
|
-
|
|
532
|
-
Parameters
|
|
533
|
-
----------
|
|
534
|
-
ax : matplotlib.axes.Axes
|
|
535
|
-
Target axes containing bar elements.
|
|
536
|
-
style : dict
|
|
537
|
-
Style dictionary with barplot_* keys.
|
|
538
|
-
"""
|
|
539
|
-
from matplotlib.patches import Rectangle
|
|
540
|
-
|
|
541
|
-
# Get edge width from style
|
|
542
|
-
edge_lw = mm_to_pt(style.get("barplot_edge_mm", 0.2))
|
|
543
|
-
|
|
544
|
-
# Bar plots create Rectangle patches
|
|
545
|
-
for patch in ax.patches:
|
|
546
|
-
if isinstance(patch, Rectangle):
|
|
547
|
-
patch.set_linewidth(edge_lw)
|
|
548
|
-
# Set edge color to black for clean scientific look
|
|
549
|
-
patch.set_edgecolor("black")
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
def _apply_histogram_style(ax: Axes, style: Dict[str, Any]) -> None:
|
|
553
|
-
"""Apply histogram edge styling to existing histogram elements.
|
|
554
|
-
|
|
555
|
-
Parameters
|
|
556
|
-
----------
|
|
557
|
-
ax : matplotlib.axes.Axes
|
|
558
|
-
Target axes containing histogram elements.
|
|
559
|
-
style : dict
|
|
560
|
-
Style dictionary with histogram_* keys.
|
|
561
|
-
"""
|
|
562
|
-
from matplotlib.patches import Rectangle
|
|
563
|
-
|
|
564
|
-
# Get edge width from style
|
|
565
|
-
edge_lw = mm_to_pt(style.get("histogram_edge_mm", 0.2))
|
|
566
|
-
|
|
567
|
-
# Histograms also create Rectangle patches
|
|
568
|
-
for patch in ax.patches:
|
|
569
|
-
if isinstance(patch, Rectangle):
|
|
570
|
-
patch.set_linewidth(edge_lw)
|
|
571
|
-
# Set edge color to black for clean scientific look
|
|
572
|
-
patch.set_edgecolor("black")
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
def _apply_pie_style(ax: Axes, style: Dict[str, Any]) -> None:
|
|
576
|
-
"""Apply pie chart styling to existing pie elements.
|
|
577
|
-
|
|
578
|
-
Parameters
|
|
579
|
-
----------
|
|
580
|
-
ax : matplotlib.axes.Axes
|
|
581
|
-
Target axes containing pie chart elements.
|
|
582
|
-
style : dict
|
|
583
|
-
Style dictionary with pie_* keys.
|
|
584
|
-
"""
|
|
585
|
-
from matplotlib.patches import Wedge
|
|
586
|
-
|
|
587
|
-
# Check if axes contains pie chart (wedge patches)
|
|
588
|
-
has_pie = any(isinstance(p, Wedge) for p in ax.patches)
|
|
589
|
-
if not has_pie:
|
|
590
|
-
return
|
|
591
|
-
|
|
592
|
-
# Get pie text size from style (default 6pt for scientific publications)
|
|
593
|
-
text_pt = style.get("pie_text_pt", 6)
|
|
594
|
-
show_axes = style.get("pie_show_axes", False)
|
|
595
|
-
font_family = check_font(style.get("font_family", "Arial"))
|
|
596
|
-
|
|
597
|
-
# Apply text size to all pie text elements (labels and percentages)
|
|
598
|
-
for text in ax.texts:
|
|
599
|
-
text.set_fontsize(text_pt)
|
|
600
|
-
text.set_fontfamily(font_family)
|
|
601
|
-
|
|
602
|
-
# Hide axes if configured (default: hide for pie charts)
|
|
603
|
-
if not show_axes:
|
|
604
|
-
ax.set_xticks([])
|
|
605
|
-
ax.set_yticks([])
|
|
606
|
-
ax.set_xticklabels([])
|
|
607
|
-
ax.set_yticklabels([])
|
|
608
|
-
# Hide spines
|
|
609
|
-
for spine in ax.spines.values():
|
|
610
|
-
spine.set_visible(False)
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
def _apply_matrix_style(ax: Axes, style: Dict[str, Any]) -> None:
|
|
614
|
-
"""Apply imshow/matshow/spy styling (hide axes if configured).
|
|
615
|
-
|
|
616
|
-
Parameters
|
|
617
|
-
----------
|
|
618
|
-
ax : matplotlib.axes.Axes
|
|
619
|
-
Target axes containing matrix plot elements.
|
|
620
|
-
style : dict
|
|
621
|
-
Style dictionary with imshow_*, matshow_*, spy_* keys.
|
|
622
|
-
"""
|
|
623
|
-
from matplotlib.image import AxesImage
|
|
624
|
-
|
|
625
|
-
# Check if axes contains an image (imshow/matshow)
|
|
626
|
-
has_image = any(isinstance(c, AxesImage) for c in ax.get_children())
|
|
627
|
-
if not has_image:
|
|
628
|
-
return
|
|
629
|
-
|
|
630
|
-
# Check if imshow_show_axes is False
|
|
631
|
-
show_axes = style.get("imshow_show_axes", True)
|
|
632
|
-
show_labels = style.get("imshow_show_labels", True)
|
|
633
|
-
|
|
634
|
-
if not show_axes:
|
|
635
|
-
ax.set_xticks([])
|
|
636
|
-
ax.set_yticks([])
|
|
637
|
-
ax.set_xticklabels([])
|
|
638
|
-
ax.set_yticklabels([])
|
|
639
|
-
# Hide spines
|
|
640
|
-
for spine in ax.spines.values():
|
|
641
|
-
spine.set_visible(False)
|
|
642
|
-
|
|
643
|
-
if not show_labels:
|
|
644
|
-
ax.set_xlabel("")
|
|
645
|
-
ax.set_ylabel("")
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
def finalize_ticks(ax: Axes) -> None:
|
|
649
|
-
"""
|
|
650
|
-
Apply deferred tick configuration after all plotting is done.
|
|
651
|
-
|
|
652
|
-
This function applies the n_ticks setting stored by apply_style_mm(),
|
|
653
|
-
but only to numeric axes (not categorical).
|
|
654
|
-
|
|
655
|
-
Parameters
|
|
656
|
-
----------
|
|
657
|
-
ax : matplotlib.axes.Axes
|
|
658
|
-
The axes to finalize.
|
|
659
|
-
"""
|
|
660
|
-
from matplotlib.ticker import MaxNLocator
|
|
661
|
-
|
|
662
|
-
n_ticks = getattr(ax, "_figrecipe_n_ticks", None)
|
|
663
|
-
if n_ticks is None:
|
|
664
|
-
return
|
|
665
|
-
|
|
666
|
-
# Check if x-axis is categorical (has string tick labels)
|
|
667
|
-
x_labels = [t.get_text() for t in ax.get_xticklabels()]
|
|
668
|
-
x_is_categorical = any(
|
|
669
|
-
lbl and not lbl.replace(".", "").replace("-", "").replace("+", "").isdigit()
|
|
670
|
-
for lbl in x_labels
|
|
671
|
-
if lbl
|
|
672
|
-
)
|
|
673
|
-
if not x_is_categorical:
|
|
674
|
-
ax.xaxis.set_major_locator(MaxNLocator(nbins=n_ticks))
|
|
675
|
-
|
|
676
|
-
# Check if y-axis is categorical
|
|
677
|
-
y_labels = [t.get_text() for t in ax.get_yticklabels()]
|
|
678
|
-
y_is_categorical = any(
|
|
679
|
-
lbl and not lbl.replace(".", "").replace("-", "").replace("+", "").isdigit()
|
|
680
|
-
for lbl in y_labels
|
|
681
|
-
if lbl
|
|
682
|
-
)
|
|
683
|
-
if not y_is_categorical:
|
|
684
|
-
ax.yaxis.set_major_locator(MaxNLocator(nbins=n_ticks))
|
|
685
|
-
|
|
686
|
-
|
|
687
248
|
if __name__ == "__main__":
|
|
688
249
|
import matplotlib.pyplot as plt
|
|
689
250
|
import numpy as np
|
|
690
251
|
|
|
691
|
-
# Test styling
|
|
692
252
|
print("Testing style application...")
|
|
693
253
|
|
|
694
254
|
fig, ax = plt.subplots(figsize=(4, 3))
|
|
@@ -714,3 +274,5 @@ if __name__ == "__main__":
|
|
|
714
274
|
plt.savefig("/tmp/test_style.png", dpi=300, bbox_inches="tight")
|
|
715
275
|
print("Saved to /tmp/test_style.png")
|
|
716
276
|
plt.close()
|
|
277
|
+
|
|
278
|
+
# EOF
|