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,480 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Style override application for preview rendering."""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
from matplotlib.axes import Axes
|
|
8
|
+
from matplotlib.figure import Figure
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def apply_overrides(
|
|
12
|
+
fig: Figure, overrides: Dict[str, Any], record: Optional[Any] = None
|
|
13
|
+
) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Apply style overrides to figure.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
fig : Figure
|
|
20
|
+
Matplotlib figure.
|
|
21
|
+
overrides : dict
|
|
22
|
+
Style overrides with keys like:
|
|
23
|
+
- axes_width_mm, axes_height_mm
|
|
24
|
+
- fonts_axis_label_pt, fonts_tick_label_pt
|
|
25
|
+
- lines_trace_mm
|
|
26
|
+
- etc.
|
|
27
|
+
record : FigureRecord, optional
|
|
28
|
+
Recording record to access call IDs for grouping elements.
|
|
29
|
+
"""
|
|
30
|
+
from ..styles._style_applier import apply_style_mm
|
|
31
|
+
|
|
32
|
+
axes_list = fig.get_axes()
|
|
33
|
+
|
|
34
|
+
for ax in axes_list:
|
|
35
|
+
# Apply mm-based styling
|
|
36
|
+
apply_style_mm(ax, overrides)
|
|
37
|
+
|
|
38
|
+
# Apply specific overrides that aren't handled by apply_style_mm
|
|
39
|
+
_apply_font_overrides(ax, overrides)
|
|
40
|
+
_apply_tick_overrides(ax, overrides)
|
|
41
|
+
_apply_behavior_overrides(ax, overrides)
|
|
42
|
+
_apply_legend_overrides(ax, overrides)
|
|
43
|
+
_apply_line_overrides(ax, overrides)
|
|
44
|
+
_apply_marker_overrides(ax, overrides)
|
|
45
|
+
|
|
46
|
+
# Apply color palette to existing elements
|
|
47
|
+
color_palette = overrides.get("color_palette")
|
|
48
|
+
if color_palette is not None:
|
|
49
|
+
ax_record = _find_ax_record(ax, axes_list, record)
|
|
50
|
+
apply_color_palette(ax, color_palette, ax_record)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _apply_font_overrides(ax: Axes, overrides: Dict[str, Any]) -> None:
|
|
54
|
+
"""Apply font-related overrides to axes."""
|
|
55
|
+
# Font sizes (YAML: fonts_axis_label_pt, legacy: axis_font_size_pt)
|
|
56
|
+
axis_fs = overrides.get("fonts_axis_label_pt", overrides.get("axis_font_size_pt"))
|
|
57
|
+
if axis_fs is not None:
|
|
58
|
+
ax.xaxis.label.set_fontsize(axis_fs)
|
|
59
|
+
ax.yaxis.label.set_fontsize(axis_fs)
|
|
60
|
+
|
|
61
|
+
tick_fs = overrides.get("fonts_tick_label_pt", overrides.get("tick_font_size_pt"))
|
|
62
|
+
if tick_fs is not None:
|
|
63
|
+
ax.tick_params(labelsize=tick_fs)
|
|
64
|
+
|
|
65
|
+
title_fs = overrides.get("fonts_title_pt", overrides.get("title_font_size_pt"))
|
|
66
|
+
if title_fs is not None:
|
|
67
|
+
ax.title.set_fontsize(title_fs)
|
|
68
|
+
|
|
69
|
+
family = overrides.get("fonts_family", overrides.get("font_family"))
|
|
70
|
+
if family is not None:
|
|
71
|
+
ax.xaxis.label.set_fontfamily(family)
|
|
72
|
+
ax.yaxis.label.set_fontfamily(family)
|
|
73
|
+
ax.title.set_fontfamily(family)
|
|
74
|
+
for label in ax.get_xticklabels() + ax.get_yticklabels():
|
|
75
|
+
label.set_fontfamily(family)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _apply_tick_overrides(ax: Axes, overrides: Dict[str, Any]) -> None:
|
|
79
|
+
"""Apply tick-related overrides to axes."""
|
|
80
|
+
# Ticks (YAML: ticks_direction, legacy: tick_direction)
|
|
81
|
+
tick_dir = overrides.get("ticks_direction", overrides.get("tick_direction"))
|
|
82
|
+
if tick_dir is not None and tick_dir in ("in", "out", "inout"):
|
|
83
|
+
ax.tick_params(direction=tick_dir)
|
|
84
|
+
|
|
85
|
+
tick_len = overrides.get("ticks_length_mm", overrides.get("tick_length_mm"))
|
|
86
|
+
if tick_len is not None:
|
|
87
|
+
from .._utils._units import mm_to_pt
|
|
88
|
+
|
|
89
|
+
length = mm_to_pt(tick_len)
|
|
90
|
+
ax.tick_params(length=length)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _apply_behavior_overrides(ax: Axes, overrides: Dict[str, Any]) -> None:
|
|
94
|
+
"""Apply behavior-related overrides (grid, spines)."""
|
|
95
|
+
# Grid (YAML: behavior_grid, legacy: grid)
|
|
96
|
+
grid_value = overrides.get("behavior_grid", overrides.get("grid"))
|
|
97
|
+
if grid_value is not None:
|
|
98
|
+
if grid_value:
|
|
99
|
+
ax.grid(True, alpha=0.3)
|
|
100
|
+
else:
|
|
101
|
+
ax.grid(False)
|
|
102
|
+
|
|
103
|
+
# Spines (YAML: behavior_hide_top_spine, legacy: hide_top_spine)
|
|
104
|
+
hide_top = overrides.get("behavior_hide_top_spine", overrides.get("hide_top_spine"))
|
|
105
|
+
if hide_top is not None:
|
|
106
|
+
ax.spines["top"].set_visible(not hide_top)
|
|
107
|
+
|
|
108
|
+
hide_right = overrides.get(
|
|
109
|
+
"behavior_hide_right_spine", overrides.get("hide_right_spine")
|
|
110
|
+
)
|
|
111
|
+
if hide_right is not None:
|
|
112
|
+
ax.spines["right"].set_visible(not hide_right)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _apply_legend_overrides(ax: Axes, overrides: Dict[str, Any]) -> None:
|
|
116
|
+
"""Apply legend-related overrides."""
|
|
117
|
+
legend = ax.get_legend()
|
|
118
|
+
if legend is not None:
|
|
119
|
+
if "legend_frameon" in overrides:
|
|
120
|
+
legend.set_frame_on(overrides["legend_frameon"])
|
|
121
|
+
|
|
122
|
+
if "legend_alpha" in overrides:
|
|
123
|
+
frame = legend.get_frame()
|
|
124
|
+
fc = frame.get_facecolor()
|
|
125
|
+
frame.set_facecolor((*fc[:3], overrides["legend_alpha"]))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _apply_line_overrides(ax: Axes, overrides: Dict[str, Any]) -> None:
|
|
129
|
+
"""Apply line-related overrides."""
|
|
130
|
+
# Line widths (YAML: lines_trace_mm, legacy: trace_thickness_mm)
|
|
131
|
+
trace_mm = overrides.get("lines_trace_mm", overrides.get("trace_thickness_mm"))
|
|
132
|
+
if trace_mm is not None:
|
|
133
|
+
from .._utils._units import mm_to_pt
|
|
134
|
+
|
|
135
|
+
lw = mm_to_pt(trace_mm)
|
|
136
|
+
for line in ax.get_lines():
|
|
137
|
+
line.set_linewidth(lw)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _apply_marker_overrides(ax: Axes, overrides: Dict[str, Any]) -> None:
|
|
141
|
+
"""Apply marker-related overrides."""
|
|
142
|
+
# Marker sizes (YAML: markers_scatter_mm, legacy: marker_size_mm)
|
|
143
|
+
# Only apply to PathCollection (scatter), not PolyCollection (violin/fill)
|
|
144
|
+
scatter_mm = overrides.get(
|
|
145
|
+
"markers_scatter_mm",
|
|
146
|
+
overrides.get("markers_size_mm", overrides.get("marker_size_mm")),
|
|
147
|
+
)
|
|
148
|
+
if scatter_mm is not None:
|
|
149
|
+
from matplotlib.collections import PathCollection
|
|
150
|
+
|
|
151
|
+
from .._utils._units import mm_to_scatter_size
|
|
152
|
+
|
|
153
|
+
size = mm_to_scatter_size(scatter_mm)
|
|
154
|
+
for coll in ax.collections:
|
|
155
|
+
# Only apply to scatter plots (PathCollection), not violin/fill (PolyCollection)
|
|
156
|
+
if isinstance(coll, PathCollection):
|
|
157
|
+
try:
|
|
158
|
+
coll.set_sizes([size])
|
|
159
|
+
except Exception:
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _find_ax_record(ax: Axes, axes_list: List[Axes], record: Optional[Any]) -> Any:
|
|
164
|
+
"""Find the AxesRecord for a given axes."""
|
|
165
|
+
if record is None or not hasattr(record, "axes"):
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
# Find ax position in the figure's axes list
|
|
169
|
+
ax_idx = axes_list.index(ax)
|
|
170
|
+
# AxesRecord keys are position tuples like "(0, 0)", "(0, 1)", etc.
|
|
171
|
+
# Try to match by index order
|
|
172
|
+
ax_keys = sorted(record.axes.keys())
|
|
173
|
+
if ax_idx < len(ax_keys):
|
|
174
|
+
return record.axes.get(ax_keys[ax_idx])
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def apply_color_palette(
|
|
179
|
+
ax: Axes, color_palette: List[Any], ax_record: Optional[Any] = None
|
|
180
|
+
) -> None:
|
|
181
|
+
"""
|
|
182
|
+
Apply color palette to existing plot elements, grouping by call_id.
|
|
183
|
+
|
|
184
|
+
Parameters
|
|
185
|
+
----------
|
|
186
|
+
ax : Axes
|
|
187
|
+
Matplotlib axes containing plot elements.
|
|
188
|
+
color_palette : list
|
|
189
|
+
List of colors (RGB tuples or color names).
|
|
190
|
+
ax_record : AxesRecord, optional
|
|
191
|
+
Record of calls for this axes (for grouping elements by call_id).
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
# Normalize colors (RGB 0-255 to 0-1)
|
|
195
|
+
normalized_palette = _normalize_color_palette(color_palette)
|
|
196
|
+
if not normalized_palette:
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
# Build call_id to color index mapping from record
|
|
200
|
+
call_color_map = _build_call_color_map(ax_record)
|
|
201
|
+
|
|
202
|
+
# Apply to different element types
|
|
203
|
+
_apply_colors_to_lines(ax, normalized_palette, ax_record, call_color_map)
|
|
204
|
+
_apply_colors_to_bars(ax, normalized_palette, ax_record, call_color_map)
|
|
205
|
+
_apply_colors_to_pie(ax, normalized_palette, ax_record)
|
|
206
|
+
_apply_colors_to_scatter(ax, normalized_palette, ax_record, call_color_map)
|
|
207
|
+
_apply_colors_to_poly(ax, normalized_palette)
|
|
208
|
+
_update_legend_colors(ax, normalized_palette)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _normalize_color_palette(color_palette: List[Any]) -> List[Any]:
|
|
212
|
+
"""Normalize color palette to 0-1 range."""
|
|
213
|
+
normalized = []
|
|
214
|
+
for c in color_palette:
|
|
215
|
+
if isinstance(c, (list, tuple)) and len(c) >= 3:
|
|
216
|
+
if all(v <= 1.0 for v in c):
|
|
217
|
+
normalized.append(tuple(c))
|
|
218
|
+
else:
|
|
219
|
+
normalized.append(tuple(v / 255.0 for v in c))
|
|
220
|
+
else:
|
|
221
|
+
normalized.append(c)
|
|
222
|
+
return normalized
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _build_call_color_map(ax_record: Optional[Any]) -> Dict[str, int]:
|
|
226
|
+
"""Build mapping from call_id to color index."""
|
|
227
|
+
call_color_map = {}
|
|
228
|
+
if ax_record:
|
|
229
|
+
color_idx = 0
|
|
230
|
+
for call in ax_record.calls:
|
|
231
|
+
if call.function in (
|
|
232
|
+
"plot",
|
|
233
|
+
"scatter",
|
|
234
|
+
"bar",
|
|
235
|
+
"barh",
|
|
236
|
+
"hist",
|
|
237
|
+
"pie",
|
|
238
|
+
"fill",
|
|
239
|
+
"fill_between",
|
|
240
|
+
"fill_betweenx",
|
|
241
|
+
):
|
|
242
|
+
if call.id not in call_color_map:
|
|
243
|
+
call_color_map[call.id] = color_idx
|
|
244
|
+
color_idx += 1
|
|
245
|
+
return call_color_map
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _apply_colors_to_lines(
|
|
249
|
+
ax: Axes,
|
|
250
|
+
palette: List[Any],
|
|
251
|
+
ax_record: Optional[Any],
|
|
252
|
+
call_color_map: Dict[str, int],
|
|
253
|
+
) -> None:
|
|
254
|
+
"""Apply colors to line elements."""
|
|
255
|
+
lines = ax.get_lines()
|
|
256
|
+
line_calls = [
|
|
257
|
+
c for c in (ax_record.calls if ax_record else []) if c.function == "plot"
|
|
258
|
+
]
|
|
259
|
+
for i, line in enumerate(lines):
|
|
260
|
+
# Skip internal lines (boxplot whiskers, etc.)
|
|
261
|
+
label = line.get_label()
|
|
262
|
+
if label.startswith("_"):
|
|
263
|
+
continue
|
|
264
|
+
if i < len(line_calls) and line_calls[i].id in call_color_map:
|
|
265
|
+
color_idx = call_color_map[line_calls[i].id]
|
|
266
|
+
else:
|
|
267
|
+
color_idx = i
|
|
268
|
+
line.set_color(palette[color_idx % len(palette)])
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _apply_colors_to_bars(
|
|
272
|
+
ax: Axes,
|
|
273
|
+
palette: List[Any],
|
|
274
|
+
ax_record: Optional[Any],
|
|
275
|
+
call_color_map: Dict[str, int],
|
|
276
|
+
) -> None:
|
|
277
|
+
"""Apply colors to bar and histogram elements."""
|
|
278
|
+
from matplotlib.patches import Rectangle
|
|
279
|
+
|
|
280
|
+
rectangles = [p for p in ax.patches if isinstance(p, Rectangle)]
|
|
281
|
+
if not rectangles:
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
bar_calls = [
|
|
285
|
+
c
|
|
286
|
+
for c in (ax_record.calls if ax_record else [])
|
|
287
|
+
if c.function in ("bar", "barh", "hist")
|
|
288
|
+
]
|
|
289
|
+
if bar_calls:
|
|
290
|
+
rect_per_call = len(rectangles) // len(bar_calls) if bar_calls else 1
|
|
291
|
+
for i, patch in enumerate(rectangles):
|
|
292
|
+
call_idx = (
|
|
293
|
+
min(i // rect_per_call, len(bar_calls) - 1) if rect_per_call > 0 else 0
|
|
294
|
+
)
|
|
295
|
+
if call_idx < len(bar_calls) and bar_calls[call_idx].id in call_color_map:
|
|
296
|
+
color_idx = call_color_map[bar_calls[call_idx].id]
|
|
297
|
+
else:
|
|
298
|
+
color_idx = call_idx
|
|
299
|
+
patch.set_facecolor(palette[color_idx % len(palette)])
|
|
300
|
+
else:
|
|
301
|
+
# No record, apply single color to all bars
|
|
302
|
+
color = palette[0]
|
|
303
|
+
for patch in rectangles:
|
|
304
|
+
patch.set_facecolor(color)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _apply_colors_to_pie(
|
|
308
|
+
ax: Axes, palette: List[Any], ax_record: Optional[Any] = None
|
|
309
|
+
) -> None:
|
|
310
|
+
"""Apply colors to pie chart wedges.
|
|
311
|
+
|
|
312
|
+
If the pie call has custom colors in kwargs, those are used.
|
|
313
|
+
Otherwise, the theme palette is applied.
|
|
314
|
+
"""
|
|
315
|
+
from matplotlib.patches import Wedge
|
|
316
|
+
|
|
317
|
+
wedges = [p for p in ax.patches if isinstance(p, Wedge)]
|
|
318
|
+
if not wedges:
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
# Check if pie call has custom colors
|
|
322
|
+
custom_colors = None
|
|
323
|
+
if ax_record:
|
|
324
|
+
for call in ax_record.calls:
|
|
325
|
+
if call.function == "pie" and "colors" in call.kwargs:
|
|
326
|
+
custom_colors = call.kwargs["colors"]
|
|
327
|
+
break
|
|
328
|
+
|
|
329
|
+
# Use custom colors if available, otherwise use palette
|
|
330
|
+
if custom_colors and isinstance(custom_colors, list):
|
|
331
|
+
# Normalize custom colors
|
|
332
|
+
colors_to_use = []
|
|
333
|
+
for c in custom_colors:
|
|
334
|
+
if isinstance(c, str):
|
|
335
|
+
colors_to_use.append(c)
|
|
336
|
+
elif isinstance(c, (list, tuple)) and len(c) >= 3:
|
|
337
|
+
if all(v <= 1.0 for v in c):
|
|
338
|
+
colors_to_use.append(tuple(c))
|
|
339
|
+
else:
|
|
340
|
+
colors_to_use.append(tuple(v / 255.0 for v in c))
|
|
341
|
+
else:
|
|
342
|
+
colors_to_use.append(c)
|
|
343
|
+
else:
|
|
344
|
+
colors_to_use = palette
|
|
345
|
+
|
|
346
|
+
for i, wedge in enumerate(wedges):
|
|
347
|
+
color = colors_to_use[i % len(colors_to_use)]
|
|
348
|
+
wedge.set_facecolor(color)
|
|
349
|
+
wedge.set_edgecolor("black")
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _apply_colors_to_scatter(
|
|
353
|
+
ax: Axes,
|
|
354
|
+
palette: List[Any],
|
|
355
|
+
ax_record: Optional[Any],
|
|
356
|
+
call_color_map: Dict[str, int],
|
|
357
|
+
) -> None:
|
|
358
|
+
"""Apply colors to scatter plot collections."""
|
|
359
|
+
from matplotlib.collections import PathCollection
|
|
360
|
+
|
|
361
|
+
scatter_collections = [c for c in ax.collections if isinstance(c, PathCollection)]
|
|
362
|
+
scatter_calls = [
|
|
363
|
+
c for c in (ax_record.calls if ax_record else []) if c.function == "scatter"
|
|
364
|
+
]
|
|
365
|
+
for i, coll in enumerate(scatter_collections):
|
|
366
|
+
if i < len(scatter_calls) and scatter_calls[i].id in call_color_map:
|
|
367
|
+
color_idx = call_color_map[scatter_calls[i].id]
|
|
368
|
+
else:
|
|
369
|
+
color_idx = i
|
|
370
|
+
coll.set_facecolor(palette[color_idx % len(palette)])
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _apply_colors_to_poly(ax: Axes, palette: List[Any]) -> None:
|
|
374
|
+
"""Apply colors to polygon collections (violin, fill)."""
|
|
375
|
+
from matplotlib.collections import PolyCollection
|
|
376
|
+
|
|
377
|
+
poly_collections = [c for c in ax.collections if isinstance(c, PolyCollection)]
|
|
378
|
+
for i, coll in enumerate(poly_collections):
|
|
379
|
+
color = palette[i % len(palette)]
|
|
380
|
+
coll.set_facecolor(color)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _update_legend_colors(ax: Axes, palette: List[Any]) -> None:
|
|
384
|
+
"""Update legend colors to reflect new palette."""
|
|
385
|
+
legend = ax.get_legend()
|
|
386
|
+
if legend is None:
|
|
387
|
+
return
|
|
388
|
+
|
|
389
|
+
handles = (
|
|
390
|
+
legend.legend_handles
|
|
391
|
+
if hasattr(legend, "legend_handles")
|
|
392
|
+
else legend.legendHandles
|
|
393
|
+
)
|
|
394
|
+
for i, handle in enumerate(handles):
|
|
395
|
+
if i < len(palette):
|
|
396
|
+
color = palette[i % len(palette)]
|
|
397
|
+
if hasattr(handle, "set_color"):
|
|
398
|
+
handle.set_color(color)
|
|
399
|
+
elif hasattr(handle, "set_facecolor"):
|
|
400
|
+
handle.set_facecolor(color)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def apply_dark_mode(fig: Figure) -> None:
|
|
404
|
+
"""
|
|
405
|
+
Apply dark mode colors to figure.
|
|
406
|
+
|
|
407
|
+
Parameters
|
|
408
|
+
----------
|
|
409
|
+
fig : Figure
|
|
410
|
+
Matplotlib figure.
|
|
411
|
+
"""
|
|
412
|
+
# Dark theme colors
|
|
413
|
+
bg_color = "#1a1a1a"
|
|
414
|
+
text_color = "#e8e8e8"
|
|
415
|
+
|
|
416
|
+
# Update rcParams for dark mode (pie charts, panel labels)
|
|
417
|
+
import matplotlib as mpl
|
|
418
|
+
|
|
419
|
+
mpl.rcParams["text.color"] = text_color
|
|
420
|
+
mpl.rcParams["axes.labelcolor"] = text_color
|
|
421
|
+
mpl.rcParams["xtick.color"] = text_color
|
|
422
|
+
mpl.rcParams["ytick.color"] = text_color
|
|
423
|
+
|
|
424
|
+
# Figure background
|
|
425
|
+
fig.patch.set_facecolor(bg_color)
|
|
426
|
+
|
|
427
|
+
# Figure-level text elements (suptitle, supxlabel, supylabel)
|
|
428
|
+
if hasattr(fig, "_suptitle") and fig._suptitle is not None:
|
|
429
|
+
fig._suptitle.set_color(text_color)
|
|
430
|
+
if hasattr(fig, "_supxlabel") and fig._supxlabel is not None:
|
|
431
|
+
fig._supxlabel.set_color(text_color)
|
|
432
|
+
if hasattr(fig, "_supylabel") and fig._supylabel is not None:
|
|
433
|
+
fig._supylabel.set_color(text_color)
|
|
434
|
+
|
|
435
|
+
for ax in fig.get_axes():
|
|
436
|
+
_apply_dark_mode_to_axes(ax, bg_color, text_color)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _apply_dark_mode_to_axes(ax: Axes, bg_color: str, text_color: str) -> None:
|
|
440
|
+
"""Apply dark mode colors to a single axes."""
|
|
441
|
+
# Axes background
|
|
442
|
+
ax.set_facecolor(bg_color)
|
|
443
|
+
|
|
444
|
+
# Text colors
|
|
445
|
+
ax.xaxis.label.set_color(text_color)
|
|
446
|
+
ax.yaxis.label.set_color(text_color)
|
|
447
|
+
ax.title.set_color(text_color)
|
|
448
|
+
|
|
449
|
+
# Tick labels and tick marks
|
|
450
|
+
ax.tick_params(colors=text_color, which="both")
|
|
451
|
+
|
|
452
|
+
# Explicitly set tick label colors (for specgram and other plots)
|
|
453
|
+
for label in ax.get_xticklabels() + ax.get_yticklabels():
|
|
454
|
+
label.set_color(text_color)
|
|
455
|
+
|
|
456
|
+
# Spines
|
|
457
|
+
for spine in ax.spines.values():
|
|
458
|
+
spine.set_color(text_color)
|
|
459
|
+
|
|
460
|
+
# All text objects on axes (panel labels, pie labels, annotations)
|
|
461
|
+
for text in ax.texts:
|
|
462
|
+
text.set_color(text_color)
|
|
463
|
+
|
|
464
|
+
# Legend
|
|
465
|
+
legend = ax.get_legend()
|
|
466
|
+
if legend is not None:
|
|
467
|
+
frame = legend.get_frame()
|
|
468
|
+
frame.set_facecolor(bg_color)
|
|
469
|
+
frame.set_edgecolor(text_color)
|
|
470
|
+
for text in legend.get_texts():
|
|
471
|
+
text.set_color(text_color)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
__all__ = [
|
|
475
|
+
"apply_overrides",
|
|
476
|
+
"apply_color_palette",
|
|
477
|
+
"apply_dark_mode",
|
|
478
|
+
]
|
|
479
|
+
|
|
480
|
+
# EOF
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Preview rendering with style overrides.
|
|
5
|
+
|
|
6
|
+
This module renders figure previews with user-specified style overrides
|
|
7
|
+
applied, enabling real-time preview updates in the GUI editor.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import io
|
|
11
|
+
from typing import Any, Dict, Optional, Tuple
|
|
12
|
+
|
|
13
|
+
from .._wrappers import RecordingFigure
|
|
14
|
+
from ._bbox import extract_bboxes
|
|
15
|
+
from ._render_overrides import apply_dark_mode, apply_overrides
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def render_preview(
|
|
19
|
+
fig: RecordingFigure,
|
|
20
|
+
overrides: Optional[Dict[str, Any]] = None,
|
|
21
|
+
dpi: int = 150,
|
|
22
|
+
dark_mode: bool = False,
|
|
23
|
+
) -> Tuple[bytes, Dict[str, Any], Tuple[int, int]]:
|
|
24
|
+
"""
|
|
25
|
+
Render figure preview with style overrides applied.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
fig : RecordingFigure
|
|
30
|
+
Figure to render.
|
|
31
|
+
overrides : dict, optional
|
|
32
|
+
Style overrides to apply.
|
|
33
|
+
dpi : int, optional
|
|
34
|
+
Render resolution (default: 150).
|
|
35
|
+
dark_mode : bool, optional
|
|
36
|
+
Whether to render in dark mode (default: False).
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
png_bytes : bytes
|
|
41
|
+
PNG image data.
|
|
42
|
+
bboxes : dict
|
|
43
|
+
Element bounding boxes.
|
|
44
|
+
img_size : tuple
|
|
45
|
+
(width, height) in pixels.
|
|
46
|
+
"""
|
|
47
|
+
# Get underlying matplotlib figure
|
|
48
|
+
mpl_fig = fig.fig if hasattr(fig, "fig") else fig
|
|
49
|
+
|
|
50
|
+
# Get record for call_id grouping (if fig is a RecordingFigure)
|
|
51
|
+
record = fig.record if hasattr(fig, "record") else None
|
|
52
|
+
|
|
53
|
+
# Apply style overrides
|
|
54
|
+
if overrides:
|
|
55
|
+
apply_overrides(mpl_fig, overrides, record)
|
|
56
|
+
|
|
57
|
+
# Apply dark mode if requested
|
|
58
|
+
if dark_mode:
|
|
59
|
+
apply_dark_mode(mpl_fig)
|
|
60
|
+
|
|
61
|
+
# Finalize ticks and special plots (must be done after all plotting)
|
|
62
|
+
_finalize_figure(fig, mpl_fig)
|
|
63
|
+
|
|
64
|
+
# Render to buffer first
|
|
65
|
+
buf = io.BytesIO()
|
|
66
|
+
mpl_fig.savefig(buf, format="png", dpi=dpi, bbox_inches="tight")
|
|
67
|
+
buf.seek(0)
|
|
68
|
+
png_bytes = buf.read()
|
|
69
|
+
|
|
70
|
+
# Get image dimensions
|
|
71
|
+
from PIL import Image
|
|
72
|
+
|
|
73
|
+
buf.seek(0)
|
|
74
|
+
img = Image.open(buf)
|
|
75
|
+
img_width, img_height = img.size
|
|
76
|
+
|
|
77
|
+
# Set figure DPI to match render DPI and force canvas redraw for accurate bbox extraction
|
|
78
|
+
original_dpi = mpl_fig.dpi
|
|
79
|
+
mpl_fig.set_dpi(dpi)
|
|
80
|
+
mpl_fig.canvas.draw()
|
|
81
|
+
|
|
82
|
+
# Extract bounding boxes (with figure DPI matching render DPI)
|
|
83
|
+
bboxes = extract_bboxes(mpl_fig, img_width, img_height)
|
|
84
|
+
|
|
85
|
+
# Restore original DPI
|
|
86
|
+
mpl_fig.set_dpi(original_dpi)
|
|
87
|
+
|
|
88
|
+
return png_bytes, bboxes, (img_width, img_height)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def render_to_base64(
|
|
92
|
+
fig: RecordingFigure,
|
|
93
|
+
overrides: Optional[Dict[str, Any]] = None,
|
|
94
|
+
dpi: int = 150,
|
|
95
|
+
dark_mode: bool = False,
|
|
96
|
+
) -> Tuple[str, Dict[str, Any], Tuple[int, int]]:
|
|
97
|
+
"""
|
|
98
|
+
Render figure preview as base64 string.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
fig : RecordingFigure
|
|
103
|
+
Figure to render.
|
|
104
|
+
overrides : dict, optional
|
|
105
|
+
Style overrides to apply.
|
|
106
|
+
dpi : int, optional
|
|
107
|
+
Render resolution (default: 150).
|
|
108
|
+
dark_mode : bool, optional
|
|
109
|
+
Whether to render in dark mode (default: False).
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
base64_str : str
|
|
114
|
+
Base64-encoded PNG image.
|
|
115
|
+
bboxes : dict
|
|
116
|
+
Element bounding boxes.
|
|
117
|
+
img_size : tuple
|
|
118
|
+
(width, height) in pixels.
|
|
119
|
+
"""
|
|
120
|
+
import base64
|
|
121
|
+
|
|
122
|
+
png_bytes, bboxes, img_size = render_preview(fig, overrides, dpi, dark_mode)
|
|
123
|
+
base64_str = base64.b64encode(png_bytes).decode("utf-8")
|
|
124
|
+
|
|
125
|
+
return base64_str, bboxes, img_size
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def render_download(
|
|
129
|
+
fig: RecordingFigure,
|
|
130
|
+
fmt: str = "png",
|
|
131
|
+
dpi: int = 300,
|
|
132
|
+
overrides: Optional[Dict[str, Any]] = None,
|
|
133
|
+
dark_mode: bool = False,
|
|
134
|
+
) -> bytes:
|
|
135
|
+
"""
|
|
136
|
+
Render figure for download in specified format.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
fig : RecordingFigure
|
|
141
|
+
Figure to render.
|
|
142
|
+
fmt : str
|
|
143
|
+
Output format: 'png', 'svg', 'pdf' (default: 'png').
|
|
144
|
+
dpi : int
|
|
145
|
+
Resolution for raster formats (default: 300).
|
|
146
|
+
overrides : dict, optional
|
|
147
|
+
Style overrides to apply.
|
|
148
|
+
dark_mode : bool, optional
|
|
149
|
+
Whether to render in dark mode.
|
|
150
|
+
|
|
151
|
+
Returns
|
|
152
|
+
-------
|
|
153
|
+
bytes
|
|
154
|
+
File content.
|
|
155
|
+
"""
|
|
156
|
+
mpl_fig = fig.fig if hasattr(fig, "fig") else fig
|
|
157
|
+
|
|
158
|
+
# Get record for call_id grouping (if fig is a RecordingFigure)
|
|
159
|
+
record = fig.record if hasattr(fig, "record") else None
|
|
160
|
+
|
|
161
|
+
if overrides:
|
|
162
|
+
apply_overrides(mpl_fig, overrides, record)
|
|
163
|
+
|
|
164
|
+
if dark_mode:
|
|
165
|
+
apply_dark_mode(mpl_fig)
|
|
166
|
+
|
|
167
|
+
# Finalize ticks and special plots (must be done after all plotting)
|
|
168
|
+
_finalize_figure(fig, mpl_fig)
|
|
169
|
+
|
|
170
|
+
buf = io.BytesIO()
|
|
171
|
+
mpl_fig.savefig(buf, format=fmt, dpi=dpi, bbox_inches="tight")
|
|
172
|
+
buf.seek(0)
|
|
173
|
+
|
|
174
|
+
return buf.read()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _finalize_figure(fig: RecordingFigure, mpl_fig: Any) -> None:
|
|
178
|
+
"""Finalize ticks and special plots for all axes in the figure."""
|
|
179
|
+
from ..styles._style_applier import finalize_special_plots, finalize_ticks
|
|
180
|
+
|
|
181
|
+
# Get style dict for finalization
|
|
182
|
+
style_dict = {}
|
|
183
|
+
if hasattr(fig, "style") and fig.style:
|
|
184
|
+
from ..styles import get_style
|
|
185
|
+
|
|
186
|
+
style_dict = get_style(fig.style)
|
|
187
|
+
|
|
188
|
+
for ax in mpl_fig.get_axes():
|
|
189
|
+
finalize_ticks(ax)
|
|
190
|
+
finalize_special_plots(ax, style_dict)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
__all__ = [
|
|
194
|
+
"render_preview",
|
|
195
|
+
"render_to_base64",
|
|
196
|
+
"render_download",
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
# EOF
|