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,177 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Collection bbox extraction for scatter, fill, and patch elements.
5
+
6
+ This module handles bbox extraction for matplotlib collections
7
+ (scatter plots, fills, bars, etc.).
8
+ """
9
+
10
+ import math
11
+ from typing import Any, Dict, Optional
12
+
13
+ from matplotlib.axes import Axes
14
+ from matplotlib.collections import PathCollection
15
+ from matplotlib.figure import Figure
16
+ from matplotlib.transforms import Bbox
17
+
18
+ from ._elements import get_element_bbox
19
+ from ._transforms import display_to_image, transform_bbox
20
+
21
+
22
+ def get_collection_bbox(
23
+ coll,
24
+ ax: Axes,
25
+ fig: Figure,
26
+ renderer,
27
+ tight_bbox: Bbox,
28
+ img_width: int,
29
+ img_height: int,
30
+ scale_x: float,
31
+ scale_y: float,
32
+ pad_inches: float,
33
+ saved_height_inches: float,
34
+ include_points: bool = True,
35
+ ) -> Optional[Dict[str, Any]]:
36
+ """Get bbox and points for a collection (scatter, fill)."""
37
+ try:
38
+ bbox = None
39
+
40
+ # For scatter plots, get_window_extent() can fail or return empty
41
+ # So we calculate bbox from data points as fallback
42
+ if isinstance(coll, PathCollection):
43
+ offsets = coll.get_offsets()
44
+ if len(offsets) > 0:
45
+ transform = ax.transData
46
+ points = []
47
+
48
+ # Limit to reasonable number of points
49
+ max_points = 200
50
+ step = max(1, len(offsets) // max_points)
51
+
52
+ for i in range(0, len(offsets), step):
53
+ try:
54
+ offset = offsets[i]
55
+ display_coords = transform.transform(offset)
56
+ img_coords = display_to_image(
57
+ display_coords[0],
58
+ display_coords[1],
59
+ fig,
60
+ tight_bbox,
61
+ img_width,
62
+ img_height,
63
+ scale_x,
64
+ scale_y,
65
+ pad_inches,
66
+ saved_height_inches,
67
+ )
68
+ if img_coords:
69
+ points.append(img_coords)
70
+ except Exception:
71
+ continue
72
+
73
+ # Calculate bbox from points
74
+ if points:
75
+ xs = [p[0] for p in points]
76
+ ys = [p[1] for p in points]
77
+ # Add padding around scatter points for easier clicking
78
+ padding = 10 # pixels
79
+ bbox = {
80
+ "x": float(min(xs) - padding),
81
+ "y": float(min(ys) - padding),
82
+ "width": float(max(xs) - min(xs) + 2 * padding),
83
+ "height": float(max(ys) - min(ys) + 2 * padding),
84
+ "points": points,
85
+ }
86
+ return bbox
87
+
88
+ # Fallback: try standard window extent
89
+ window_extent = coll.get_window_extent(renderer)
90
+ if window_extent is None:
91
+ # Use axes extent as fallback
92
+ return get_element_bbox(
93
+ ax,
94
+ fig,
95
+ renderer,
96
+ tight_bbox,
97
+ img_width,
98
+ img_height,
99
+ scale_x,
100
+ scale_y,
101
+ pad_inches,
102
+ saved_height_inches,
103
+ )
104
+
105
+ # Check if window_extent is valid (not inf)
106
+ if (
107
+ math.isinf(window_extent.x0)
108
+ or math.isinf(window_extent.y0)
109
+ or math.isinf(window_extent.x1)
110
+ or math.isinf(window_extent.y1)
111
+ ):
112
+ # Invalid extent - use axes extent as fallback
113
+ return get_element_bbox(
114
+ ax,
115
+ fig,
116
+ renderer,
117
+ tight_bbox,
118
+ img_width,
119
+ img_height,
120
+ scale_x,
121
+ scale_y,
122
+ pad_inches,
123
+ saved_height_inches,
124
+ )
125
+
126
+ bbox = transform_bbox(
127
+ window_extent,
128
+ fig,
129
+ tight_bbox,
130
+ img_width,
131
+ img_height,
132
+ scale_x,
133
+ scale_y,
134
+ pad_inches,
135
+ saved_height_inches,
136
+ )
137
+
138
+ return bbox
139
+
140
+ except Exception:
141
+ return None
142
+
143
+
144
+ def get_patch_bbox(
145
+ patch,
146
+ ax: Axes,
147
+ fig: Figure,
148
+ renderer,
149
+ tight_bbox: Bbox,
150
+ img_width: int,
151
+ img_height: int,
152
+ scale_x: float,
153
+ scale_y: float,
154
+ pad_inches: float,
155
+ saved_height_inches: float,
156
+ ) -> Optional[Dict[str, float]]:
157
+ """Get bbox for a patch (bar, rectangle)."""
158
+ try:
159
+ window_extent = patch.get_window_extent(renderer)
160
+ if window_extent is None:
161
+ return None
162
+ return transform_bbox(
163
+ window_extent,
164
+ fig,
165
+ tight_bbox,
166
+ img_width,
167
+ img_height,
168
+ scale_x,
169
+ scale_y,
170
+ pad_inches,
171
+ saved_height_inches,
172
+ )
173
+ except Exception:
174
+ return None
175
+
176
+
177
+ __all__ = ["get_collection_bbox", "get_patch_bbox"]
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Element bbox extraction for general elements, text, and ticks.
5
+
6
+ This module handles bbox extraction for axes, text labels, and tick marks.
7
+ """
8
+
9
+ from typing import Dict, Optional
10
+
11
+ from matplotlib.figure import Figure
12
+ from matplotlib.transforms import Bbox
13
+
14
+ from ._transforms import transform_bbox
15
+
16
+
17
+ def get_element_bbox(
18
+ element,
19
+ fig: Figure,
20
+ renderer,
21
+ tight_bbox: Bbox,
22
+ img_width: int,
23
+ img_height: int,
24
+ scale_x: float,
25
+ scale_y: float,
26
+ pad_inches: float,
27
+ saved_height_inches: float,
28
+ ) -> Optional[Dict[str, float]]:
29
+ """Get bbox for a general element."""
30
+ try:
31
+ window_extent = element.get_window_extent(renderer)
32
+ if window_extent is None:
33
+ return None
34
+ return transform_bbox(
35
+ window_extent,
36
+ fig,
37
+ tight_bbox,
38
+ img_width,
39
+ img_height,
40
+ scale_x,
41
+ scale_y,
42
+ pad_inches,
43
+ saved_height_inches,
44
+ )
45
+ except Exception:
46
+ return None
47
+
48
+
49
+ def get_text_bbox(
50
+ text,
51
+ fig: Figure,
52
+ renderer,
53
+ tight_bbox: Bbox,
54
+ img_width: int,
55
+ img_height: int,
56
+ scale_x: float,
57
+ scale_y: float,
58
+ pad_inches: float,
59
+ saved_height_inches: float,
60
+ ) -> Optional[Dict[str, float]]:
61
+ """Get bbox for a text element."""
62
+ try:
63
+ window_extent = text.get_window_extent(renderer)
64
+ if window_extent is None:
65
+ return None
66
+ return transform_bbox(
67
+ window_extent,
68
+ fig,
69
+ tight_bbox,
70
+ img_width,
71
+ img_height,
72
+ scale_x,
73
+ scale_y,
74
+ pad_inches,
75
+ saved_height_inches,
76
+ )
77
+ except Exception:
78
+ return None
79
+
80
+
81
+ def get_tick_labels_bbox(
82
+ axis,
83
+ axis_type: str, # 'x' or 'y'
84
+ fig: Figure,
85
+ renderer,
86
+ tight_bbox: Bbox,
87
+ img_width: int,
88
+ img_height: int,
89
+ scale_x: float,
90
+ scale_y: float,
91
+ pad_inches: float,
92
+ saved_height_inches: float,
93
+ ) -> Optional[Dict[str, float]]:
94
+ """
95
+ Get bbox for tick labels, extended to span the full axis dimension.
96
+
97
+ For x-axis: tick labels bbox spans the full width of the plot area.
98
+ For y-axis: tick labels bbox spans the full height of the plot area.
99
+ """
100
+ try:
101
+ all_bboxes = []
102
+
103
+ # Get all tick label bboxes
104
+ for tick in axis.get_major_ticks():
105
+ tick_label = tick.label1 if hasattr(tick, "label1") else tick.label
106
+ if tick_label and tick_label.get_visible():
107
+ try:
108
+ tick_extent = tick_label.get_window_extent(renderer)
109
+ if tick_extent is not None and tick_extent.width > 0:
110
+ all_bboxes.append(tick_extent)
111
+ except Exception:
112
+ pass
113
+
114
+ if not all_bboxes:
115
+ return None
116
+
117
+ # Merge all tick label bboxes
118
+ merged = all_bboxes[0]
119
+ for bbox in all_bboxes[1:]:
120
+ merged = Bbox.union([merged, bbox])
121
+
122
+ # Get the axes extent to extend the tick labels region
123
+ ax = axis.axes
124
+ ax_bbox = ax.get_window_extent(renderer)
125
+
126
+ if axis_type == "x":
127
+ # For x-axis: extend width to match axes width, keep tick labels height
128
+ merged = Bbox.from_extents(
129
+ ax_bbox.x0, # Align left with axes
130
+ merged.y0, # Keep tick labels y position
131
+ ax_bbox.x1, # Align right with axes
132
+ merged.y1, # Keep tick labels height
133
+ )
134
+ else: # y-axis
135
+ # For y-axis: extend height to match axes height, keep tick labels width
136
+ merged = Bbox.from_extents(
137
+ merged.x0, # Keep tick labels x position
138
+ ax_bbox.y0, # Align bottom with axes
139
+ merged.x1, # Keep tick labels width
140
+ ax_bbox.y1, # Align top with axes
141
+ )
142
+
143
+ return transform_bbox(
144
+ merged,
145
+ fig,
146
+ tight_bbox,
147
+ img_width,
148
+ img_height,
149
+ scale_x,
150
+ scale_y,
151
+ pad_inches,
152
+ saved_height_inches,
153
+ )
154
+
155
+ except Exception:
156
+ return None
157
+
158
+
159
+ __all__ = ["get_element_bbox", "get_text_bbox", "get_tick_labels_bbox"]
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Main bounding box extraction function.
5
+
6
+ This module contains the main extract_bboxes function that coordinates
7
+ all element-specific bbox extractors.
8
+ """
9
+
10
+ from typing import Any, Dict
11
+
12
+ from matplotlib.figure import Figure
13
+ from matplotlib.transforms import Bbox
14
+
15
+ from ._elements import get_element_bbox
16
+ from ._extract_axes import (
17
+ extract_collections,
18
+ extract_images,
19
+ extract_lines,
20
+ extract_patches,
21
+ )
22
+ from ._extract_text import (
23
+ extract_figure_text,
24
+ extract_legend,
25
+ extract_spines,
26
+ extract_text_elements,
27
+ )
28
+
29
+
30
+ def extract_bboxes(
31
+ fig: Figure,
32
+ img_width: int,
33
+ img_height: int,
34
+ include_points: bool = True,
35
+ ) -> Dict[str, Dict[str, Any]]:
36
+ """
37
+ Extract bounding boxes for all figure elements.
38
+
39
+ Parameters
40
+ ----------
41
+ fig : matplotlib.figure.Figure
42
+ Figure to extract bboxes from.
43
+ img_width : int
44
+ Width of the output image in pixels.
45
+ img_height : int
46
+ Height of the output image in pixels.
47
+ include_points : bool, optional
48
+ Whether to include point arrays for lines/scatter (default: True).
49
+
50
+ Returns
51
+ -------
52
+ dict
53
+ Mapping from element key to bbox info.
54
+ """
55
+ bboxes = {}
56
+
57
+ # Get renderer for bbox calculations
58
+ fig.canvas.draw()
59
+ renderer = fig.canvas.get_renderer()
60
+
61
+ # Get tight bbox for coordinate transformation
62
+ tight_bbox = fig.get_tightbbox(renderer)
63
+ if tight_bbox is None:
64
+ tight_bbox = Bbox.from_bounds(0, 0, fig.get_figwidth(), fig.get_figheight())
65
+
66
+ # bbox_inches='tight' adds pad_inches (default 0.1) around the tight bbox
67
+ pad_inches = 0.1
68
+ saved_width_inches = tight_bbox.width + 2 * pad_inches
69
+ saved_height_inches = tight_bbox.height + 2 * pad_inches
70
+
71
+ # Calculate scale factors from saved image size to pixel size
72
+ scale_x = img_width / saved_width_inches if saved_width_inches > 0 else 1
73
+ scale_y = img_height / saved_height_inches if saved_height_inches > 0 else 1
74
+
75
+ # Process each axes
76
+ for ax_idx, ax in enumerate(fig.get_axes()):
77
+ _extract_axes_bboxes(
78
+ ax,
79
+ ax_idx,
80
+ fig,
81
+ renderer,
82
+ tight_bbox,
83
+ img_width,
84
+ img_height,
85
+ scale_x,
86
+ scale_y,
87
+ pad_inches,
88
+ saved_height_inches,
89
+ include_points,
90
+ bboxes,
91
+ )
92
+
93
+ # Process figure-level text elements
94
+ extract_figure_text(
95
+ fig,
96
+ renderer,
97
+ tight_bbox,
98
+ img_width,
99
+ img_height,
100
+ scale_x,
101
+ scale_y,
102
+ pad_inches,
103
+ saved_height_inches,
104
+ bboxes,
105
+ )
106
+
107
+ # Add metadata
108
+ bboxes["_meta"] = {
109
+ "img_width": img_width,
110
+ "img_height": img_height,
111
+ "fig_width_inches": fig.get_figwidth(),
112
+ "fig_height_inches": fig.get_figheight(),
113
+ "dpi": fig.dpi,
114
+ }
115
+
116
+ return bboxes
117
+
118
+
119
+ def _extract_axes_bboxes(
120
+ ax,
121
+ ax_idx,
122
+ fig,
123
+ renderer,
124
+ tight_bbox,
125
+ img_width,
126
+ img_height,
127
+ scale_x,
128
+ scale_y,
129
+ pad_inches,
130
+ saved_height_inches,
131
+ include_points,
132
+ bboxes,
133
+ ):
134
+ """Extract bboxes for all elements within an axes."""
135
+ # Axes bounding box
136
+ ax_bbox = get_element_bbox(
137
+ ax,
138
+ fig,
139
+ renderer,
140
+ tight_bbox,
141
+ img_width,
142
+ img_height,
143
+ scale_x,
144
+ scale_y,
145
+ pad_inches,
146
+ saved_height_inches,
147
+ )
148
+ if ax_bbox:
149
+ bboxes[f"ax{ax_idx}_axes"] = {**ax_bbox, "type": "axes", "ax_index": ax_idx}
150
+
151
+ # Extract all element types
152
+ extract_lines(
153
+ ax,
154
+ ax_idx,
155
+ fig,
156
+ renderer,
157
+ tight_bbox,
158
+ img_width,
159
+ img_height,
160
+ scale_x,
161
+ scale_y,
162
+ pad_inches,
163
+ saved_height_inches,
164
+ include_points,
165
+ bboxes,
166
+ )
167
+ extract_collections(
168
+ ax,
169
+ ax_idx,
170
+ fig,
171
+ renderer,
172
+ tight_bbox,
173
+ img_width,
174
+ img_height,
175
+ scale_x,
176
+ scale_y,
177
+ pad_inches,
178
+ saved_height_inches,
179
+ include_points,
180
+ bboxes,
181
+ )
182
+ extract_patches(
183
+ ax,
184
+ ax_idx,
185
+ fig,
186
+ renderer,
187
+ tight_bbox,
188
+ img_width,
189
+ img_height,
190
+ scale_x,
191
+ scale_y,
192
+ pad_inches,
193
+ saved_height_inches,
194
+ bboxes,
195
+ )
196
+ extract_images(
197
+ ax,
198
+ ax_idx,
199
+ fig,
200
+ renderer,
201
+ tight_bbox,
202
+ img_width,
203
+ img_height,
204
+ scale_x,
205
+ scale_y,
206
+ pad_inches,
207
+ saved_height_inches,
208
+ bboxes,
209
+ )
210
+ extract_text_elements(
211
+ ax,
212
+ ax_idx,
213
+ fig,
214
+ renderer,
215
+ tight_bbox,
216
+ img_width,
217
+ img_height,
218
+ scale_x,
219
+ scale_y,
220
+ pad_inches,
221
+ saved_height_inches,
222
+ bboxes,
223
+ )
224
+ extract_legend(
225
+ ax,
226
+ ax_idx,
227
+ fig,
228
+ renderer,
229
+ tight_bbox,
230
+ img_width,
231
+ img_height,
232
+ scale_x,
233
+ scale_y,
234
+ pad_inches,
235
+ saved_height_inches,
236
+ bboxes,
237
+ )
238
+ extract_spines(
239
+ ax,
240
+ ax_idx,
241
+ fig,
242
+ renderer,
243
+ tight_bbox,
244
+ img_width,
245
+ img_height,
246
+ scale_x,
247
+ scale_y,
248
+ pad_inches,
249
+ saved_height_inches,
250
+ bboxes,
251
+ )
252
+
253
+
254
+ __all__ = ["extract_bboxes"]
255
+
256
+ # EOF