figrecipe 0.5.0__py3-none-any.whl → 0.7.4__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 (189) hide show
  1. figrecipe/__init__.py +220 -819
  2. figrecipe/_api/__init__.py +48 -0
  3. figrecipe/_api/_extract.py +108 -0
  4. figrecipe/_api/_notebook.py +61 -0
  5. figrecipe/_api/_panel.py +46 -0
  6. figrecipe/_api/_save.py +191 -0
  7. figrecipe/_api/_seaborn_proxy.py +34 -0
  8. figrecipe/_api/_style_manager.py +153 -0
  9. figrecipe/_api/_subplots.py +333 -0
  10. figrecipe/_api/_validate.py +82 -0
  11. figrecipe/_dev/__init__.py +29 -0
  12. figrecipe/_dev/_plotters.py +76 -0
  13. figrecipe/_dev/_run_demos.py +56 -0
  14. figrecipe/_dev/demo_plotters/__init__.py +64 -0
  15. figrecipe/_dev/demo_plotters/_categories.py +81 -0
  16. figrecipe/_dev/demo_plotters/_figure_creators.py +119 -0
  17. figrecipe/_dev/demo_plotters/_helpers.py +31 -0
  18. figrecipe/_dev/demo_plotters/_registry.py +50 -0
  19. figrecipe/_dev/demo_plotters/bar_categorical/__init__.py +4 -0
  20. figrecipe/_dev/demo_plotters/bar_categorical/plot_bar.py +25 -0
  21. figrecipe/_dev/demo_plotters/bar_categorical/plot_barh.py +25 -0
  22. figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
  23. figrecipe/_dev/demo_plotters/contour_surface/plot_contour.py +30 -0
  24. figrecipe/_dev/demo_plotters/contour_surface/plot_contourf.py +29 -0
  25. figrecipe/_dev/demo_plotters/contour_surface/plot_tricontour.py +28 -0
  26. figrecipe/_dev/demo_plotters/contour_surface/plot_tricontourf.py +28 -0
  27. figrecipe/_dev/demo_plotters/contour_surface/plot_tripcolor.py +29 -0
  28. figrecipe/_dev/demo_plotters/contour_surface/plot_triplot.py +25 -0
  29. figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
  30. figrecipe/_dev/demo_plotters/distribution/plot_boxplot.py +24 -0
  31. figrecipe/_dev/demo_plotters/distribution/plot_ecdf.py +24 -0
  32. figrecipe/_dev/demo_plotters/distribution/plot_hist.py +24 -0
  33. figrecipe/_dev/demo_plotters/distribution/plot_hist2d.py +25 -0
  34. figrecipe/_dev/demo_plotters/distribution/plot_violinplot.py +25 -0
  35. figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
  36. figrecipe/_dev/demo_plotters/image_matrix/plot_hexbin.py +25 -0
  37. figrecipe/_dev/demo_plotters/image_matrix/plot_imshow.py +23 -0
  38. figrecipe/_dev/demo_plotters/image_matrix/plot_matshow.py +23 -0
  39. figrecipe/_dev/demo_plotters/image_matrix/plot_pcolor.py +29 -0
  40. figrecipe/_dev/demo_plotters/image_matrix/plot_pcolormesh.py +29 -0
  41. figrecipe/_dev/demo_plotters/image_matrix/plot_spy.py +29 -0
  42. figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
  43. figrecipe/_dev/demo_plotters/line_curve/plot_errorbar.py +28 -0
  44. figrecipe/_dev/demo_plotters/line_curve/plot_fill.py +29 -0
  45. figrecipe/_dev/demo_plotters/line_curve/plot_fill_between.py +30 -0
  46. figrecipe/_dev/demo_plotters/line_curve/plot_fill_betweenx.py +28 -0
  47. figrecipe/_dev/demo_plotters/line_curve/plot_plot.py +28 -0
  48. figrecipe/_dev/demo_plotters/line_curve/plot_stackplot.py +29 -0
  49. figrecipe/_dev/demo_plotters/line_curve/plot_stairs.py +27 -0
  50. figrecipe/_dev/demo_plotters/line_curve/plot_step.py +27 -0
  51. figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
  52. figrecipe/_dev/demo_plotters/scatter_points/plot_scatter.py +24 -0
  53. figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
  54. figrecipe/_dev/demo_plotters/special/plot_eventplot.py +25 -0
  55. figrecipe/_dev/demo_plotters/special/plot_loglog.py +27 -0
  56. figrecipe/_dev/demo_plotters/special/plot_pie.py +27 -0
  57. figrecipe/_dev/demo_plotters/special/plot_semilogx.py +27 -0
  58. figrecipe/_dev/demo_plotters/special/plot_semilogy.py +27 -0
  59. figrecipe/_dev/demo_plotters/special/plot_stem.py +27 -0
  60. figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
  61. figrecipe/_dev/demo_plotters/spectral_signal/plot_acorr.py +24 -0
  62. figrecipe/_dev/demo_plotters/spectral_signal/plot_angle_spectrum.py +28 -0
  63. figrecipe/_dev/demo_plotters/spectral_signal/plot_cohere.py +29 -0
  64. figrecipe/_dev/demo_plotters/spectral_signal/plot_csd.py +29 -0
  65. figrecipe/_dev/demo_plotters/spectral_signal/plot_magnitude_spectrum.py +28 -0
  66. figrecipe/_dev/demo_plotters/spectral_signal/plot_phase_spectrum.py +28 -0
  67. figrecipe/_dev/demo_plotters/spectral_signal/plot_psd.py +29 -0
  68. figrecipe/_dev/demo_plotters/spectral_signal/plot_specgram.py +30 -0
  69. figrecipe/_dev/demo_plotters/spectral_signal/plot_xcorr.py +25 -0
  70. figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
  71. figrecipe/_dev/demo_plotters/vector_flow/plot_barbs.py +30 -0
  72. figrecipe/_dev/demo_plotters/vector_flow/plot_quiver.py +30 -0
  73. figrecipe/_dev/demo_plotters/vector_flow/plot_streamplot.py +30 -0
  74. figrecipe/_editor/__init__.py +278 -0
  75. figrecipe/_editor/_bbox/__init__.py +43 -0
  76. figrecipe/_editor/_bbox/_collections.py +177 -0
  77. figrecipe/_editor/_bbox/_elements.py +159 -0
  78. figrecipe/_editor/_bbox/_extract.py +256 -0
  79. figrecipe/_editor/_bbox/_extract_axes.py +370 -0
  80. figrecipe/_editor/_bbox/_extract_text.py +342 -0
  81. figrecipe/_editor/_bbox/_lines.py +173 -0
  82. figrecipe/_editor/_bbox/_transforms.py +146 -0
  83. figrecipe/_editor/_flask_app.py +258 -0
  84. figrecipe/_editor/_helpers.py +242 -0
  85. figrecipe/_editor/_hitmap/__init__.py +76 -0
  86. figrecipe/_editor/_hitmap/_artists/__init__.py +21 -0
  87. figrecipe/_editor/_hitmap/_artists/_collections.py +345 -0
  88. figrecipe/_editor/_hitmap/_artists/_images.py +68 -0
  89. figrecipe/_editor/_hitmap/_artists/_lines.py +107 -0
  90. figrecipe/_editor/_hitmap/_artists/_patches.py +163 -0
  91. figrecipe/_editor/_hitmap/_artists/_text.py +190 -0
  92. figrecipe/_editor/_hitmap/_colors.py +181 -0
  93. figrecipe/_editor/_hitmap/_detect.py +137 -0
  94. figrecipe/_editor/_hitmap/_restore.py +154 -0
  95. figrecipe/_editor/_hitmap_main.py +182 -0
  96. figrecipe/_editor/_overrides.py +318 -0
  97. figrecipe/_editor/_preferences.py +135 -0
  98. figrecipe/_editor/_render_overrides.py +480 -0
  99. figrecipe/_editor/_renderer.py +199 -0
  100. figrecipe/_editor/_routes_axis.py +453 -0
  101. figrecipe/_editor/_routes_core.py +284 -0
  102. figrecipe/_editor/_routes_element.py +317 -0
  103. figrecipe/_editor/_routes_style.py +223 -0
  104. figrecipe/_editor/_templates/__init__.py +152 -0
  105. figrecipe/_editor/_templates/_html.py +502 -0
  106. figrecipe/_editor/_templates/_scripts/__init__.py +120 -0
  107. figrecipe/_editor/_templates/_scripts/_api.py +228 -0
  108. figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
  109. figrecipe/_editor/_templates/_scripts/_core.py +436 -0
  110. figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
  111. figrecipe/_editor/_templates/_scripts/_element_editor.py +310 -0
  112. figrecipe/_editor/_templates/_scripts/_files.py +195 -0
  113. figrecipe/_editor/_templates/_scripts/_hitmap.py +509 -0
  114. figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
  115. figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
  116. figrecipe/_editor/_templates/_scripts/_legend_drag.py +265 -0
  117. figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
  118. figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
  119. figrecipe/_editor/_templates/_scripts/_panel_drag.py +334 -0
  120. figrecipe/_editor/_templates/_scripts/_panel_position.py +279 -0
  121. figrecipe/_editor/_templates/_scripts/_selection.py +237 -0
  122. figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
  123. figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
  124. figrecipe/_editor/_templates/_scripts/_zoom.py +179 -0
  125. figrecipe/_editor/_templates/_styles/__init__.py +69 -0
  126. figrecipe/_editor/_templates/_styles/_base.py +64 -0
  127. figrecipe/_editor/_templates/_styles/_buttons.py +206 -0
  128. figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
  129. figrecipe/_editor/_templates/_styles/_controls.py +265 -0
  130. figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
  131. figrecipe/_editor/_templates/_styles/_forms.py +126 -0
  132. figrecipe/_editor/_templates/_styles/_hitmap.py +184 -0
  133. figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
  134. figrecipe/_editor/_templates/_styles/_labels.py +118 -0
  135. figrecipe/_editor/_templates/_styles/_modals.py +98 -0
  136. figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
  137. figrecipe/_editor/_templates/_styles/_preview.py +225 -0
  138. figrecipe/_editor/_templates/_styles/_selection.py +73 -0
  139. figrecipe/_params/_DECORATION_METHODS.py +33 -0
  140. figrecipe/_params/_PLOTTING_METHODS.py +58 -0
  141. figrecipe/_params/__init__.py +9 -0
  142. figrecipe/_recorder.py +92 -110
  143. figrecipe/_recorder_utils.py +124 -0
  144. figrecipe/_reproducer/__init__.py +18 -0
  145. figrecipe/_reproducer/_core.py +498 -0
  146. figrecipe/_reproducer/_custom_plots.py +279 -0
  147. figrecipe/_reproducer/_seaborn.py +100 -0
  148. figrecipe/_reproducer/_violin.py +186 -0
  149. figrecipe/_seaborn.py +14 -9
  150. figrecipe/_serializer.py +2 -2
  151. figrecipe/_signatures/README.md +68 -0
  152. figrecipe/_signatures/__init__.py +12 -2
  153. figrecipe/_signatures/_kwargs.py +273 -0
  154. figrecipe/_signatures/_loader.py +114 -57
  155. figrecipe/_signatures/_parsing.py +147 -0
  156. figrecipe/_utils/__init__.py +6 -4
  157. figrecipe/_utils/_crop.py +10 -4
  158. figrecipe/_utils/_image_diff.py +37 -33
  159. figrecipe/_utils/_numpy_io.py +0 -1
  160. figrecipe/_utils/_units.py +11 -3
  161. figrecipe/_validator.py +12 -3
  162. figrecipe/_wrappers/_axes.py +193 -170
  163. figrecipe/_wrappers/_axes_helpers.py +136 -0
  164. figrecipe/_wrappers/_axes_plots.py +418 -0
  165. figrecipe/_wrappers/_axes_seaborn.py +157 -0
  166. figrecipe/_wrappers/_figure.py +277 -18
  167. figrecipe/_wrappers/_panel_labels.py +127 -0
  168. figrecipe/_wrappers/_plot_helpers.py +143 -0
  169. figrecipe/_wrappers/_violin_helpers.py +180 -0
  170. figrecipe/plt.py +0 -1
  171. figrecipe/pyplot.py +2 -1
  172. figrecipe/styles/__init__.py +12 -11
  173. figrecipe/styles/_dotdict.py +72 -0
  174. figrecipe/styles/_finalize.py +134 -0
  175. figrecipe/styles/_fonts.py +77 -0
  176. figrecipe/styles/_kwargs_converter.py +178 -0
  177. figrecipe/styles/_plot_styles.py +209 -0
  178. figrecipe/styles/_style_applier.py +60 -202
  179. figrecipe/styles/_style_loader.py +73 -121
  180. figrecipe/styles/_themes.py +151 -0
  181. figrecipe/styles/presets/MATPLOTLIB.yaml +95 -0
  182. figrecipe/styles/presets/SCITEX.yaml +181 -0
  183. figrecipe-0.7.4.dist-info/METADATA +429 -0
  184. figrecipe-0.7.4.dist-info/RECORD +188 -0
  185. figrecipe/_reproducer.py +0 -358
  186. figrecipe-0.5.0.dist-info/METADATA +0 -336
  187. figrecipe-0.5.0.dist-info/RECORD +0 -26
  188. {figrecipe-0.5.0.dist-info → figrecipe-0.7.4.dist-info}/WHEEL +0 -0
  189. {figrecipe-0.5.0.dist-info → figrecipe-0.7.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Convert style DotDict to subplots kwargs."""
4
+
5
+ from typing import TYPE_CHECKING, Any, Dict, Optional
6
+
7
+ if TYPE_CHECKING:
8
+ from ._dotdict import DotDict
9
+
10
+
11
+ def to_subplots_kwargs(style: Optional["DotDict"] = None) -> Dict[str, Any]:
12
+ """Convert style DotDict to kwargs for ps.subplots().
13
+
14
+ Uses YAML-compatible flattened key names as the single source of truth.
15
+ For example, YAML `fonts.axis_label_pt` becomes `fonts_axis_label_pt`.
16
+
17
+ Parameters
18
+ ----------
19
+ style : DotDict, optional
20
+ Style configuration. If None, uses current loaded style.
21
+
22
+ Returns
23
+ -------
24
+ dict
25
+ Keyword arguments for ps.subplots() with YAML-compatible flattened keys.
26
+
27
+ Examples
28
+ --------
29
+ >>> style = load_style()
30
+ >>> kwargs = to_subplots_kwargs(style)
31
+ >>> fig, ax = ps.subplots(**kwargs)
32
+ """
33
+ if style is None:
34
+ from ._style_loader import get_style
35
+
36
+ style = get_style()
37
+
38
+ # YAML-compatible flattened keys (single source of truth)
39
+ result = {
40
+ # Axes (axes.* in YAML)
41
+ "axes_width_mm": style.axes.width_mm,
42
+ "axes_height_mm": style.axes.height_mm,
43
+ "axes_thickness_mm": style.axes.thickness_mm,
44
+ # Margins (margins.* in YAML)
45
+ "margins_left_mm": style.margins.left_mm,
46
+ "margins_right_mm": style.margins.right_mm,
47
+ "margins_bottom_mm": style.margins.bottom_mm,
48
+ "margins_top_mm": style.margins.top_mm,
49
+ # Spacing (spacing.* in YAML)
50
+ "spacing_horizontal_mm": style.spacing.horizontal_mm,
51
+ "spacing_vertical_mm": style.spacing.vertical_mm,
52
+ # Ticks (ticks.* in YAML)
53
+ "ticks_length_mm": style.ticks.length_mm,
54
+ "ticks_thickness_mm": style.ticks.thickness_mm,
55
+ "ticks_direction": style.ticks.get("direction", "out"),
56
+ "ticks_n_ticks_min": style.ticks.get("n_ticks_min", 3),
57
+ "ticks_n_ticks_max": style.ticks.get("n_ticks_max", 4),
58
+ # Lines (lines.* in YAML)
59
+ "lines_trace_mm": style.lines.trace_mm,
60
+ "lines_errorbar_mm": style.lines.get("errorbar_mm", 0.2),
61
+ "lines_errorbar_cap_mm": style.lines.get("errorbar_cap_mm", 0.8),
62
+ # Markers (markers.* in YAML)
63
+ "markers_size_mm": style.markers.size_mm,
64
+ "markers_scatter_mm": style.markers.get("scatter_mm", style.markers.size_mm),
65
+ "markers_flier_mm": style.markers.get("flier_mm", style.markers.size_mm),
66
+ "markers_edge_width_mm": style.markers.get("edge_width_mm"),
67
+ # Boxplot (boxplot.* in YAML)
68
+ "boxplot_line_mm": style.get("boxplot", {}).get("line_mm", 0.2),
69
+ "boxplot_whisker_mm": style.get("boxplot", {}).get("whisker_mm", 0.2),
70
+ "boxplot_cap_mm": style.get("boxplot", {}).get("cap_mm", 0.2),
71
+ "boxplot_median_mm": style.get("boxplot", {}).get("median_mm", 0.2),
72
+ "boxplot_median_color": style.get("boxplot", {}).get("median_color", "black"),
73
+ "boxplot_flier_edge_mm": style.get("boxplot", {}).get("flier_edge_mm", 0.2),
74
+ # Violinplot (violinplot.* in YAML)
75
+ "violinplot_line_mm": style.get("violinplot", {}).get("line_mm", 0.2),
76
+ "violinplot_inner": style.get("violinplot", {}).get("inner", "box"),
77
+ "violinplot_box_width_mm": style.get("violinplot", {}).get("box_width_mm", 1.5),
78
+ "violinplot_whisker_mm": style.get("violinplot", {}).get("whisker_mm", 0.2),
79
+ "violinplot_median_mm": style.get("violinplot", {}).get("median_mm", 0.8),
80
+ # Barplot (barplot.* in YAML)
81
+ "barplot_edge_mm": style.get("barplot", {}).get("edge_mm", 0.2),
82
+ # Histogram (histogram.* in YAML)
83
+ "histogram_edge_mm": style.get("histogram", {}).get("edge_mm", 0.2),
84
+ # Pie chart (pie.* in YAML)
85
+ "pie_text_pt": style.get("pie", {}).get("text_pt", 6),
86
+ "pie_show_axes": style.get("pie", {}).get("show_axes", False),
87
+ # Imshow (imshow.* in YAML)
88
+ "imshow_show_axes": style.get("imshow", {}).get("show_axes", False),
89
+ "imshow_show_labels": style.get("imshow", {}).get("show_labels", False),
90
+ # Fonts (fonts.* in YAML)
91
+ "fonts_family": style.fonts.family,
92
+ "fonts_axis_label_pt": style.fonts.axis_label_pt,
93
+ "fonts_tick_label_pt": style.fonts.tick_label_pt,
94
+ "fonts_title_pt": style.fonts.title_pt,
95
+ "fonts_suptitle_pt": style.fonts.suptitle_pt,
96
+ "fonts_legend_pt": style.fonts.legend_pt,
97
+ "fonts_annotation_pt": style.fonts.get("annotation_pt", 6),
98
+ # Padding (padding.* in YAML)
99
+ "padding_label_pt": style.padding.label_pt,
100
+ "padding_tick_pt": style.padding.tick_pt,
101
+ "padding_title_pt": style.padding.title_pt,
102
+ # Output (output.* in YAML)
103
+ "output_dpi": style.output.dpi,
104
+ "output_transparent": style.output.get("transparent", True),
105
+ "output_format": style.output.get("format", "pdf"),
106
+ # Theme (theme.* in YAML)
107
+ "theme_mode": style.theme.mode,
108
+ }
109
+
110
+ # Add theme colors from preset if available
111
+ theme_mode = style.theme.mode
112
+ if "theme" in style and theme_mode in style.theme:
113
+ result["theme_colors"] = dict(style.theme[theme_mode])
114
+
115
+ # Add color palette if available
116
+ if "colors" in style and "palette" in style.colors:
117
+ result["color_palette"] = list(style.colors.palette)
118
+
119
+ # Add behavior settings (behavior.* in YAML)
120
+ if "behavior" in style:
121
+ behavior = style.behavior
122
+ if hasattr(behavior, "hide_top_spine"):
123
+ result["behavior_hide_top_spine"] = behavior.hide_top_spine
124
+ if hasattr(behavior, "hide_right_spine"):
125
+ result["behavior_hide_right_spine"] = behavior.hide_right_spine
126
+ if hasattr(behavior, "grid"):
127
+ result["behavior_grid"] = behavior.grid
128
+ if hasattr(behavior, "auto_scale_axes"):
129
+ result["behavior_auto_scale_axes"] = behavior.auto_scale_axes
130
+ if hasattr(behavior, "constrained_layout"):
131
+ result["behavior_constrained_layout"] = behavior.constrained_layout
132
+
133
+ # Legacy key aliases for backwards compatibility
134
+ result.update(_get_legacy_aliases(result))
135
+
136
+ return result
137
+
138
+
139
+ def _get_legacy_aliases(result: Dict[str, Any]) -> Dict[str, Any]:
140
+ """Get legacy key aliases for backwards compatibility.
141
+
142
+ These allow existing code using old keys to still work.
143
+ """
144
+ return {
145
+ "margin_left_mm": result["margins_left_mm"],
146
+ "margin_right_mm": result["margins_right_mm"],
147
+ "margin_bottom_mm": result["margins_bottom_mm"],
148
+ "margin_top_mm": result["margins_top_mm"],
149
+ "space_w_mm": result["spacing_horizontal_mm"],
150
+ "space_h_mm": result["spacing_vertical_mm"],
151
+ "tick_length_mm": result["ticks_length_mm"],
152
+ "tick_thickness_mm": result["ticks_thickness_mm"],
153
+ "n_ticks_min": result["ticks_n_ticks_min"],
154
+ "n_ticks_max": result["ticks_n_ticks_max"],
155
+ "trace_thickness_mm": result["lines_trace_mm"],
156
+ "marker_size_mm": result["markers_size_mm"],
157
+ "font_family": result["fonts_family"],
158
+ "axis_font_size_pt": result["fonts_axis_label_pt"],
159
+ "tick_font_size_pt": result["fonts_tick_label_pt"],
160
+ "title_font_size_pt": result["fonts_title_pt"],
161
+ "suptitle_font_size_pt": result["fonts_suptitle_pt"],
162
+ "legend_font_size_pt": result["fonts_legend_pt"],
163
+ "label_pad_pt": result["padding_label_pt"],
164
+ "tick_pad_pt": result["padding_tick_pt"],
165
+ "title_pad_pt": result["padding_title_pt"],
166
+ "dpi": result["output_dpi"],
167
+ "theme": result["theme_mode"],
168
+ "hide_top_spine": result.get("behavior_hide_top_spine", True),
169
+ "hide_right_spine": result.get("behavior_hide_right_spine", True),
170
+ "grid": result.get("behavior_grid", False),
171
+ "auto_scale_axes": result.get("behavior_auto_scale_axes", True),
172
+ "constrained_layout": result.get("behavior_constrained_layout", False),
173
+ }
174
+
175
+
176
+ __all__ = ["to_subplots_kwargs"]
177
+
178
+ # EOF
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Plot-specific style application for figrecipe."""
4
+
5
+ from typing import Any, Dict
6
+
7
+ from matplotlib.axes import Axes
8
+
9
+ from .._utils._units import mm_to_pt
10
+ from ._fonts import check_font
11
+
12
+
13
+ def apply_boxplot_style(ax: Axes, style: Dict[str, Any]) -> None:
14
+ """Apply boxplot line width styling to existing boxplot elements.
15
+
16
+ Parameters
17
+ ----------
18
+ ax : matplotlib.axes.Axes
19
+ Target axes containing boxplot elements.
20
+ style : dict
21
+ Style dictionary with boxplot_* keys.
22
+ """
23
+ from matplotlib.lines import Line2D
24
+ from matplotlib.patches import PathPatch
25
+
26
+ box_lw = mm_to_pt(style.get("boxplot_line_mm", 0.2))
27
+ whisker_lw = mm_to_pt(style.get("boxplot_whisker_mm", 0.2))
28
+ cap_lw = mm_to_pt(style.get("boxplot_cap_mm", 0.2))
29
+ median_lw = mm_to_pt(style.get("boxplot_median_mm", 0.2))
30
+ median_color = style.get("boxplot_median_color", "black")
31
+ flier_edge_lw = mm_to_pt(style.get("boxplot_flier_edge_mm", 0.2))
32
+
33
+ for child in ax.get_children():
34
+ if isinstance(child, PathPatch):
35
+ if child.get_edgecolor() is not None:
36
+ child.set_linewidth(box_lw)
37
+
38
+ elif isinstance(child, Line2D):
39
+ xdata = child.get_xdata()
40
+ ydata = child.get_ydata()
41
+
42
+ marker = child.get_marker()
43
+ linestyle = child.get_linestyle()
44
+ if marker and marker != "None" and linestyle in ("None", "", " "):
45
+ child.set_markeredgewidth(flier_edge_lw)
46
+ elif len(xdata) == 2 and len(ydata) == 2:
47
+ if ydata[0] == ydata[1]:
48
+ if linestyle == "-":
49
+ child.set_linewidth(median_lw)
50
+ if median_color:
51
+ child.set_color(median_color)
52
+ else:
53
+ child.set_linewidth(cap_lw)
54
+ elif xdata[0] == xdata[1]:
55
+ child.set_linewidth(whisker_lw)
56
+
57
+
58
+ def apply_violinplot_style(ax: Axes, style: Dict[str, Any]) -> None:
59
+ """Apply violinplot line width styling to existing violinplot elements.
60
+
61
+ Parameters
62
+ ----------
63
+ ax : matplotlib.axes.Axes
64
+ Target axes containing violinplot elements.
65
+ style : dict
66
+ Style dictionary with violinplot_* keys.
67
+ """
68
+ from matplotlib.collections import LineCollection, PolyCollection
69
+
70
+ body_lw = mm_to_pt(style.get("violinplot_line_mm", 0.2))
71
+ whisker_lw = mm_to_pt(style.get("violinplot_whisker_mm", 0.2))
72
+
73
+ for child in ax.get_children():
74
+ if isinstance(child, PolyCollection):
75
+ child.set_linewidth(body_lw)
76
+ elif isinstance(child, LineCollection):
77
+ child.set_linewidth(whisker_lw)
78
+
79
+
80
+ def apply_barplot_style(ax: Axes, style: Dict[str, Any]) -> None:
81
+ """Apply barplot edge styling to existing bar elements.
82
+
83
+ Parameters
84
+ ----------
85
+ ax : matplotlib.axes.Axes
86
+ Target axes containing bar elements.
87
+ style : dict
88
+ Style dictionary with barplot_* keys.
89
+ """
90
+ from matplotlib.patches import Rectangle
91
+
92
+ edge_lw = mm_to_pt(style.get("barplot_edge_mm", 0.2))
93
+
94
+ for patch in ax.patches:
95
+ if isinstance(patch, Rectangle):
96
+ patch.set_linewidth(edge_lw)
97
+ patch.set_edgecolor("black")
98
+
99
+
100
+ def apply_histogram_style(ax: Axes, style: Dict[str, Any]) -> None:
101
+ """Apply histogram edge styling to existing histogram elements.
102
+
103
+ Parameters
104
+ ----------
105
+ ax : matplotlib.axes.Axes
106
+ Target axes containing histogram elements.
107
+ style : dict
108
+ Style dictionary with histogram_* keys.
109
+ """
110
+ from matplotlib.patches import Rectangle
111
+
112
+ edge_lw = mm_to_pt(style.get("histogram_edge_mm", 0.2))
113
+
114
+ for patch in ax.patches:
115
+ if isinstance(patch, Rectangle):
116
+ patch.set_linewidth(edge_lw)
117
+ patch.set_edgecolor("black")
118
+
119
+
120
+ def apply_pie_style(ax: Axes, style: Dict[str, Any]) -> None:
121
+ """Apply pie chart styling to existing pie elements.
122
+
123
+ Parameters
124
+ ----------
125
+ ax : matplotlib.axes.Axes
126
+ Target axes containing pie chart elements.
127
+ style : dict
128
+ Style dictionary with pie_* keys.
129
+ """
130
+ from matplotlib.patches import Wedge
131
+
132
+ has_pie = any(isinstance(p, Wedge) for p in ax.patches)
133
+ if not has_pie:
134
+ return
135
+
136
+ text_pt = style.get("pie_text_pt", 6)
137
+ show_axes = style.get("pie_show_axes", False)
138
+ font_family = check_font(style.get("font_family", "Arial"))
139
+
140
+ for text in ax.texts:
141
+ transform = text.get_transform()
142
+ if transform == ax.transAxes:
143
+ x, y = text.get_position()
144
+ if y > 1.0 or y < 0.0:
145
+ continue
146
+ text.set_fontsize(text_pt)
147
+ text.set_fontfamily(font_family)
148
+
149
+ if not show_axes:
150
+ ax.set_xticks([])
151
+ ax.set_yticks([])
152
+ ax.set_xticklabels([])
153
+ ax.set_yticklabels([])
154
+ for spine in ax.spines.values():
155
+ spine.set_visible(False)
156
+
157
+
158
+ def apply_matrix_style(ax: Axes, style: Dict[str, Any]) -> None:
159
+ """Apply imshow/matshow/spy styling (hide axes if configured).
160
+
161
+ Parameters
162
+ ----------
163
+ ax : matplotlib.axes.Axes
164
+ Target axes containing matrix plot elements.
165
+ style : dict
166
+ Style dictionary with imshow_*, matshow_*, spy_* keys.
167
+ """
168
+ from matplotlib.image import AxesImage
169
+
170
+ has_image = any(isinstance(c, AxesImage) for c in ax.get_children())
171
+ if not has_image:
172
+ return
173
+
174
+ # Check if this is specgram (has xlabel or ylabel)
175
+ # Specgram typically has "Time" and "Frequency" labels
176
+ xlabel = ax.get_xlabel()
177
+ ylabel = ax.get_ylabel()
178
+ is_specgram = bool(xlabel or ylabel)
179
+
180
+ # Don't hide axes for specgram - it needs visible ticks
181
+ if is_specgram:
182
+ return
183
+
184
+ show_axes = style.get("imshow_show_axes", True)
185
+ show_labels = style.get("imshow_show_labels", True)
186
+
187
+ if not show_axes:
188
+ ax.set_xticks([])
189
+ ax.set_yticks([])
190
+ ax.set_xticklabels([])
191
+ ax.set_yticklabels([])
192
+ for spine in ax.spines.values():
193
+ spine.set_visible(False)
194
+
195
+ if not show_labels:
196
+ ax.set_xlabel("")
197
+ ax.set_ylabel("")
198
+
199
+
200
+ __all__ = [
201
+ "apply_boxplot_style",
202
+ "apply_violinplot_style",
203
+ "apply_barplot_style",
204
+ "apply_histogram_style",
205
+ "apply_pie_style",
206
+ "apply_matrix_style",
207
+ ]
208
+
209
+ # EOF