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.
- figrecipe/__init__.py +220 -819
- figrecipe/_api/__init__.py +48 -0
- figrecipe/_api/_extract.py +108 -0
- figrecipe/_api/_notebook.py +61 -0
- figrecipe/_api/_panel.py +46 -0
- figrecipe/_api/_save.py +191 -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/_dev/__init__.py +29 -0
- figrecipe/_dev/_plotters.py +76 -0
- figrecipe/_dev/_run_demos.py +56 -0
- figrecipe/_dev/demo_plotters/__init__.py +64 -0
- 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/bar_categorical/plot_bar.py +25 -0
- figrecipe/_dev/demo_plotters/bar_categorical/plot_barh.py +25 -0
- figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_contour.py +30 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_contourf.py +29 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_tricontour.py +28 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_tricontourf.py +28 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_tripcolor.py +29 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_triplot.py +25 -0
- figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/distribution/plot_boxplot.py +24 -0
- figrecipe/_dev/demo_plotters/distribution/plot_ecdf.py +24 -0
- figrecipe/_dev/demo_plotters/distribution/plot_hist.py +24 -0
- figrecipe/_dev/demo_plotters/distribution/plot_hist2d.py +25 -0
- figrecipe/_dev/demo_plotters/distribution/plot_violinplot.py +25 -0
- figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_hexbin.py +25 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_imshow.py +23 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_matshow.py +23 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_pcolor.py +29 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_pcolormesh.py +29 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_spy.py +29 -0
- figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_errorbar.py +28 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_fill.py +29 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_fill_between.py +30 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_fill_betweenx.py +28 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_plot.py +28 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_stackplot.py +29 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_stairs.py +27 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_step.py +27 -0
- figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/scatter_points/plot_scatter.py +24 -0
- figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/special/plot_eventplot.py +25 -0
- figrecipe/_dev/demo_plotters/special/plot_loglog.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_pie.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_semilogx.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_semilogy.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_stem.py +27 -0
- figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_acorr.py +24 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_angle_spectrum.py +28 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_cohere.py +29 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_csd.py +29 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_magnitude_spectrum.py +28 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_phase_spectrum.py +28 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_psd.py +29 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_specgram.py +30 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_xcorr.py +25 -0
- figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/vector_flow/plot_barbs.py +30 -0
- figrecipe/_dev/demo_plotters/vector_flow/plot_quiver.py +30 -0
- figrecipe/_dev/demo_plotters/vector_flow/plot_streamplot.py +30 -0
- figrecipe/_editor/__init__.py +278 -0
- 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 +256 -0
- figrecipe/_editor/_bbox/_extract_axes.py +370 -0
- figrecipe/_editor/_bbox/_extract_text.py +342 -0
- figrecipe/_editor/_bbox/_lines.py +173 -0
- figrecipe/_editor/_bbox/_transforms.py +146 -0
- figrecipe/_editor/_flask_app.py +258 -0
- figrecipe/_editor/_helpers.py +242 -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 +137 -0
- figrecipe/_editor/_hitmap/_restore.py +154 -0
- figrecipe/_editor/_hitmap_main.py +182 -0
- figrecipe/_editor/_overrides.py +318 -0
- figrecipe/_editor/_preferences.py +135 -0
- figrecipe/_editor/_render_overrides.py +480 -0
- figrecipe/_editor/_renderer.py +199 -0
- figrecipe/_editor/_routes_axis.py +453 -0
- figrecipe/_editor/_routes_core.py +284 -0
- figrecipe/_editor/_routes_element.py +317 -0
- figrecipe/_editor/_routes_style.py +223 -0
- figrecipe/_editor/_templates/__init__.py +152 -0
- figrecipe/_editor/_templates/_html.py +502 -0
- figrecipe/_editor/_templates/_scripts/__init__.py +120 -0
- figrecipe/_editor/_templates/_scripts/_api.py +228 -0
- figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
- figrecipe/_editor/_templates/_scripts/_core.py +436 -0
- figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
- figrecipe/_editor/_templates/_scripts/_element_editor.py +310 -0
- figrecipe/_editor/_templates/_scripts/_files.py +195 -0
- figrecipe/_editor/_templates/_scripts/_hitmap.py +509 -0
- figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
- figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
- figrecipe/_editor/_templates/_scripts/_legend_drag.py +265 -0
- figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
- figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag.py +334 -0
- figrecipe/_editor/_templates/_scripts/_panel_position.py +279 -0
- figrecipe/_editor/_templates/_scripts/_selection.py +237 -0
- figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
- figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
- figrecipe/_editor/_templates/_scripts/_zoom.py +179 -0
- figrecipe/_editor/_templates/_styles/__init__.py +69 -0
- figrecipe/_editor/_templates/_styles/_base.py +64 -0
- figrecipe/_editor/_templates/_styles/_buttons.py +206 -0
- figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
- figrecipe/_editor/_templates/_styles/_controls.py +265 -0
- figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
- figrecipe/_editor/_templates/_styles/_forms.py +126 -0
- figrecipe/_editor/_templates/_styles/_hitmap.py +184 -0
- figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
- figrecipe/_editor/_templates/_styles/_labels.py +118 -0
- figrecipe/_editor/_templates/_styles/_modals.py +98 -0
- figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
- figrecipe/_editor/_templates/_styles/_preview.py +225 -0
- figrecipe/_editor/_templates/_styles/_selection.py +73 -0
- figrecipe/_params/_DECORATION_METHODS.py +33 -0
- figrecipe/_params/_PLOTTING_METHODS.py +58 -0
- figrecipe/_params/__init__.py +9 -0
- figrecipe/_recorder.py +92 -110
- figrecipe/_recorder_utils.py +124 -0
- figrecipe/_reproducer/__init__.py +18 -0
- figrecipe/_reproducer/_core.py +498 -0
- figrecipe/_reproducer/_custom_plots.py +279 -0
- figrecipe/_reproducer/_seaborn.py +100 -0
- figrecipe/_reproducer/_violin.py +186 -0
- figrecipe/_seaborn.py +14 -9
- figrecipe/_serializer.py +2 -2
- figrecipe/_signatures/README.md +68 -0
- figrecipe/_signatures/__init__.py +12 -2
- figrecipe/_signatures/_kwargs.py +273 -0
- figrecipe/_signatures/_loader.py +114 -57
- figrecipe/_signatures/_parsing.py +147 -0
- figrecipe/_utils/__init__.py +6 -4
- figrecipe/_utils/_crop.py +10 -4
- figrecipe/_utils/_image_diff.py +37 -33
- figrecipe/_utils/_numpy_io.py +0 -1
- figrecipe/_utils/_units.py +11 -3
- figrecipe/_validator.py +12 -3
- figrecipe/_wrappers/_axes.py +193 -170
- figrecipe/_wrappers/_axes_helpers.py +136 -0
- figrecipe/_wrappers/_axes_plots.py +418 -0
- figrecipe/_wrappers/_axes_seaborn.py +157 -0
- figrecipe/_wrappers/_figure.py +277 -18
- figrecipe/_wrappers/_panel_labels.py +127 -0
- figrecipe/_wrappers/_plot_helpers.py +143 -0
- figrecipe/_wrappers/_violin_helpers.py +180 -0
- figrecipe/plt.py +0 -1
- figrecipe/pyplot.py +2 -1
- figrecipe/styles/__init__.py +12 -11
- 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 +60 -202
- figrecipe/styles/_style_loader.py +73 -121
- figrecipe/styles/_themes.py +151 -0
- figrecipe/styles/presets/MATPLOTLIB.yaml +95 -0
- figrecipe/styles/presets/SCITEX.yaml +181 -0
- figrecipe-0.7.4.dist-info/METADATA +429 -0
- figrecipe-0.7.4.dist-info/RECORD +188 -0
- figrecipe/_reproducer.py +0 -358
- figrecipe-0.5.0.dist-info/METADATA +0 -336
- figrecipe-0.5.0.dist-info/RECORD +0 -26
- {figrecipe-0.5.0.dist-info → figrecipe-0.7.4.dist-info}/WHEEL +0 -0
- {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
|