figrecipe 0.6.0__py3-none-any.whl → 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- figrecipe/__init__.py +161 -1030
- figrecipe/__main__.py +12 -0
- figrecipe/_api/__init__.py +48 -0
- figrecipe/_api/_extract.py +108 -0
- figrecipe/_api/_notebook.py +61 -0
- figrecipe/_api/_panel.py +113 -0
- figrecipe/_api/_save.py +287 -0
- figrecipe/_api/_seaborn_proxy.py +34 -0
- figrecipe/_api/_style_manager.py +153 -0
- figrecipe/_api/_subplots.py +333 -0
- figrecipe/_api/_validate.py +82 -0
- figrecipe/_cli/__init__.py +7 -0
- figrecipe/_cli/_compose.py +87 -0
- figrecipe/_cli/_convert.py +117 -0
- figrecipe/_cli/_crop.py +82 -0
- figrecipe/_cli/_edit.py +70 -0
- figrecipe/_cli/_extract.py +128 -0
- figrecipe/_cli/_fonts.py +47 -0
- figrecipe/_cli/_info.py +67 -0
- figrecipe/_cli/_main.py +58 -0
- figrecipe/_cli/_reproduce.py +79 -0
- figrecipe/_cli/_style.py +77 -0
- figrecipe/_cli/_validate.py +66 -0
- figrecipe/_cli/_version.py +50 -0
- figrecipe/_composition/__init__.py +32 -0
- figrecipe/_composition/_alignment.py +452 -0
- figrecipe/_composition/_compose.py +179 -0
- figrecipe/_composition/_import_axes.py +127 -0
- figrecipe/_composition/_visibility.py +125 -0
- figrecipe/_dev/__init__.py +4 -93
- figrecipe/_dev/_plotters.py +76 -0
- figrecipe/_dev/_run_demos.py +56 -0
- figrecipe/_dev/browser/__init__.py +69 -0
- figrecipe/_dev/browser/_audio.py +240 -0
- figrecipe/_dev/browser/_caption.py +356 -0
- figrecipe/_dev/browser/_click_effect.py +146 -0
- figrecipe/_dev/browser/_cursor.py +196 -0
- figrecipe/_dev/browser/_highlight.py +105 -0
- figrecipe/_dev/browser/_narration.py +237 -0
- figrecipe/_dev/browser/_recorder.py +446 -0
- figrecipe/_dev/browser/_utils.py +178 -0
- figrecipe/_dev/browser/_video_trim/__init__.py +152 -0
- figrecipe/_dev/browser/_video_trim/_detection.py +223 -0
- figrecipe/_dev/browser/_video_trim/_markers.py +140 -0
- figrecipe/_dev/demo_plotters/__init__.py +35 -166
- figrecipe/_dev/demo_plotters/_categories.py +81 -0
- figrecipe/_dev/demo_plotters/_figure_creators.py +119 -0
- figrecipe/_dev/demo_plotters/_helpers.py +31 -0
- figrecipe/_dev/demo_plotters/_registry.py +50 -0
- figrecipe/_dev/demo_plotters/bar_categorical/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/{plot_plot.py → line_curve/plot_plot.py} +3 -2
- figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/{plot_pie.py → special/plot_pie.py} +5 -1
- figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
- figrecipe/_editor/__init__.py +61 -13
- figrecipe/_editor/_bbox/__init__.py +43 -0
- figrecipe/_editor/_bbox/_collections.py +177 -0
- figrecipe/_editor/_bbox/_elements.py +159 -0
- figrecipe/_editor/_bbox/_extract.py +402 -0
- figrecipe/_editor/_bbox/_extract_axes.py +370 -0
- figrecipe/_editor/_bbox/_extract_text.py +466 -0
- figrecipe/_editor/_bbox/_lines.py +173 -0
- figrecipe/_editor/_bbox/_transforms.py +146 -0
- figrecipe/_editor/_call_overrides.py +183 -0
- figrecipe/_editor/_datatable_plot_handlers.py +249 -0
- figrecipe/_editor/_figure_layout.py +211 -0
- figrecipe/_editor/_flask_app.py +200 -1030
- figrecipe/_editor/_helpers.py +251 -0
- figrecipe/_editor/_hitmap/__init__.py +76 -0
- figrecipe/_editor/_hitmap/_artists/__init__.py +21 -0
- figrecipe/_editor/_hitmap/_artists/_collections.py +345 -0
- figrecipe/_editor/_hitmap/_artists/_images.py +68 -0
- figrecipe/_editor/_hitmap/_artists/_lines.py +107 -0
- figrecipe/_editor/_hitmap/_artists/_patches.py +163 -0
- figrecipe/_editor/_hitmap/_artists/_text.py +190 -0
- figrecipe/_editor/_hitmap/_colors.py +181 -0
- figrecipe/_editor/_hitmap/_detect.py +194 -0
- figrecipe/_editor/_hitmap/_restore.py +154 -0
- figrecipe/_editor/_hitmap_main.py +182 -0
- figrecipe/_editor/_overrides.py +4 -1
- figrecipe/_editor/_plot_types_registry.py +190 -0
- figrecipe/_editor/_preferences.py +135 -0
- figrecipe/_editor/_render_overrides.py +507 -0
- figrecipe/_editor/_renderer.py +81 -186
- figrecipe/_editor/_routes_annotation.py +114 -0
- figrecipe/_editor/_routes_axis.py +482 -0
- figrecipe/_editor/_routes_captions.py +130 -0
- figrecipe/_editor/_routes_composition.py +270 -0
- figrecipe/_editor/_routes_core.py +126 -0
- figrecipe/_editor/_routes_datatable.py +364 -0
- figrecipe/_editor/_routes_element.py +335 -0
- figrecipe/_editor/_routes_files.py +443 -0
- figrecipe/_editor/_routes_image.py +200 -0
- figrecipe/_editor/_routes_snapshot.py +94 -0
- figrecipe/_editor/_routes_style.py +243 -0
- figrecipe/_editor/_templates/__init__.py +116 -1
- figrecipe/_editor/_templates/_html.py +154 -64
- figrecipe/_editor/_templates/_html_components/__init__.py +13 -0
- figrecipe/_editor/_templates/_html_components/_composition_toolbar.py +79 -0
- figrecipe/_editor/_templates/_html_components/_file_browser.py +41 -0
- figrecipe/_editor/_templates/_html_datatable.py +92 -0
- figrecipe/_editor/_templates/_scripts/__init__.py +178 -0
- figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
- figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
- figrecipe/_editor/_templates/_scripts/_api.py +228 -0
- figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
- figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
- figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
- figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
- figrecipe/_editor/_templates/_scripts/_core.py +493 -0
- figrecipe/_editor/_templates/_scripts/_datatable/__init__.py +59 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_cell_edit.py +97 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_clipboard.py +164 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_context_menu.py +221 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_core.py +150 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_editable.py +511 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_import.py +161 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_plot.py +261 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_selection.py +438 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_table.py +256 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_tabs.py +354 -0
- figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
- figrecipe/_editor/_templates/_scripts/_element_editor.py +325 -0
- figrecipe/_editor/_templates/_scripts/_files.py +429 -0
- figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
- figrecipe/_editor/_templates/_scripts/_hitmap.py +512 -0
- figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
- figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
- figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
- figrecipe/_editor/_templates/_scripts/_legend_drag.py +270 -0
- figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
- figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
- figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag.py +505 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
- figrecipe/_editor/_templates/_scripts/_panel_position.py +463 -0
- figrecipe/_editor/_templates/_scripts/_panel_resize.py +230 -0
- figrecipe/_editor/_templates/_scripts/_panel_snap.py +307 -0
- figrecipe/_editor/_templates/_scripts/_region_select.py +255 -0
- figrecipe/_editor/_templates/_scripts/_selection.py +244 -0
- figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
- figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
- figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
- figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
- figrecipe/_editor/_templates/_scripts/_zoom.py +212 -0
- figrecipe/_editor/_templates/_styles/__init__.py +78 -0
- figrecipe/_editor/_templates/_styles/_base.py +111 -0
- figrecipe/_editor/_templates/_styles/_buttons.py +327 -0
- figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
- figrecipe/_editor/_templates/_styles/_composition.py +87 -0
- figrecipe/_editor/_templates/_styles/_controls.py +430 -0
- figrecipe/_editor/_templates/_styles/_datatable/__init__.py +40 -0
- figrecipe/_editor/_templates/_styles/_datatable/_editable.py +203 -0
- figrecipe/_editor/_templates/_styles/_datatable/_panel.py +268 -0
- figrecipe/_editor/_templates/_styles/_datatable/_table.py +479 -0
- figrecipe/_editor/_templates/_styles/_datatable/_toolbar.py +384 -0
- figrecipe/_editor/_templates/_styles/_datatable/_vars.py +123 -0
- figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
- figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
- figrecipe/_editor/_templates/_styles/_forms.py +224 -0
- figrecipe/_editor/_templates/_styles/_hitmap.py +191 -0
- figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
- figrecipe/_editor/_templates/_styles/_labels.py +118 -0
- figrecipe/_editor/_templates/_styles/_modals.py +127 -0
- figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
- figrecipe/_editor/_templates/_styles/_preview.py +430 -0
- figrecipe/_editor/_templates/_styles/_selection.py +73 -0
- figrecipe/_editor/_templates/_styles/_spinner.py +117 -0
- figrecipe/_editor/static/audio/click.mp3 +0 -0
- figrecipe/_editor/static/click.mp3 +0 -0
- figrecipe/_editor/static/icons/favicon.ico +0 -0
- figrecipe/_integrations/__init__.py +17 -0
- figrecipe/_integrations/_scitex_stats.py +298 -0
- figrecipe/_params/_DECORATION_METHODS.py +8 -0
- figrecipe/_recorder.py +63 -109
- figrecipe/_recorder_utils.py +124 -0
- figrecipe/_reproducer/__init__.py +18 -0
- figrecipe/_reproducer/_core.py +509 -0
- figrecipe/_reproducer/_custom_plots.py +279 -0
- figrecipe/_reproducer/_seaborn.py +100 -0
- figrecipe/_reproducer/_violin.py +186 -0
- figrecipe/_signatures/_kwargs.py +273 -0
- figrecipe/_signatures/_loader.py +21 -423
- figrecipe/_signatures/_parsing.py +147 -0
- figrecipe/_utils/__init__.py +3 -0
- figrecipe/_utils/_bundle.py +205 -0
- figrecipe/_wrappers/_axes.py +252 -895
- figrecipe/_wrappers/_axes_helpers.py +136 -0
- figrecipe/_wrappers/_axes_plots.py +418 -0
- figrecipe/_wrappers/_axes_seaborn.py +157 -0
- figrecipe/_wrappers/_caption_generator.py +218 -0
- figrecipe/_wrappers/_figure.py +188 -1
- figrecipe/_wrappers/_panel_labels.py +127 -0
- figrecipe/_wrappers/_plot_helpers.py +143 -0
- figrecipe/_wrappers/_stat_annotation.py +274 -0
- figrecipe/_wrappers/_violin_helpers.py +180 -0
- figrecipe/styles/__init__.py +8 -6
- figrecipe/styles/_dotdict.py +72 -0
- figrecipe/styles/_finalize.py +134 -0
- figrecipe/styles/_fonts.py +77 -0
- figrecipe/styles/_kwargs_converter.py +178 -0
- figrecipe/styles/_plot_styles.py +209 -0
- figrecipe/styles/_style_applier.py +42 -480
- figrecipe/styles/_style_loader.py +16 -192
- figrecipe/styles/_themes.py +151 -0
- figrecipe/styles/presets/MATPLOTLIB.yaml +2 -1
- figrecipe/styles/presets/SCITEX.yaml +40 -28
- figrecipe-0.9.0.dist-info/METADATA +427 -0
- figrecipe-0.9.0.dist-info/RECORD +277 -0
- figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
- figrecipe/_editor/_bbox.py +0 -978
- figrecipe/_editor/_hitmap.py +0 -937
- figrecipe/_editor/_templates/_scripts.py +0 -2778
- figrecipe/_editor/_templates/_styles.py +0 -1326
- figrecipe/_reproducer.py +0 -975
- figrecipe-0.6.0.dist-info/METADATA +0 -394
- figrecipe-0.6.0.dist-info/RECORD +0 -90
- /figrecipe/_dev/demo_plotters/{plot_bar.py → bar_categorical/plot_bar.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_barh.py → bar_categorical/plot_barh.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_contour.py → contour_surface/plot_contour.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_contourf.py → contour_surface/plot_contourf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tricontour.py → contour_surface/plot_tricontour.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tricontourf.py → contour_surface/plot_tricontourf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tripcolor.py → contour_surface/plot_tripcolor.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_triplot.py → contour_surface/plot_triplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_boxplot.py → distribution/plot_boxplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_ecdf.py → distribution/plot_ecdf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hist.py → distribution/plot_hist.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hist2d.py → distribution/plot_hist2d.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_violinplot.py → distribution/plot_violinplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hexbin.py → image_matrix/plot_hexbin.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_imshow.py → image_matrix/plot_imshow.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_matshow.py → image_matrix/plot_matshow.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_pcolor.py → image_matrix/plot_pcolor.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_pcolormesh.py → image_matrix/plot_pcolormesh.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_spy.py → image_matrix/plot_spy.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_errorbar.py → line_curve/plot_errorbar.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill.py → line_curve/plot_fill.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill_between.py → line_curve/plot_fill_between.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill_betweenx.py → line_curve/plot_fill_betweenx.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stackplot.py → line_curve/plot_stackplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stairs.py → line_curve/plot_stairs.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_step.py → line_curve/plot_step.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_scatter.py → scatter_points/plot_scatter.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_eventplot.py → special/plot_eventplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_loglog.py → special/plot_loglog.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_semilogx.py → special/plot_semilogx.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_semilogy.py → special/plot_semilogy.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stem.py → special/plot_stem.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_acorr.py → spectral_signal/plot_acorr.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_angle_spectrum.py → spectral_signal/plot_angle_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_cohere.py → spectral_signal/plot_cohere.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_csd.py → spectral_signal/plot_csd.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_magnitude_spectrum.py → spectral_signal/plot_magnitude_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_phase_spectrum.py → spectral_signal/plot_phase_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_psd.py → spectral_signal/plot_psd.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_specgram.py → spectral_signal/plot_specgram.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_xcorr.py → spectral_signal/plot_xcorr.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_barbs.py → vector_flow/plot_barbs.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_quiver.py → vector_flow/plot_quiver.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_streamplot.py → vector_flow/plot_streamplot.py} +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Caption generation utilities for scientific figures."""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
6
|
+
|
|
7
|
+
from ._stat_annotation import p_to_stars
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def format_stats_value(value: float, precision: int = 2) -> str:
|
|
11
|
+
"""Format a statistical value for display."""
|
|
12
|
+
if abs(value) >= 1000 or (abs(value) < 0.01 and value != 0):
|
|
13
|
+
return f"{value:.{precision}e}"
|
|
14
|
+
return f"{value:.{precision}f}"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def format_comparison(comp: Dict[str, Any], style: str = "publication") -> str:
|
|
18
|
+
"""Format a single comparison result.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
comp : dict
|
|
23
|
+
Comparison dict with keys like: name, p_value, stars, effect_size, method.
|
|
24
|
+
style : str
|
|
25
|
+
"publication", "brief", or "detailed".
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
str
|
|
30
|
+
Formatted comparison string.
|
|
31
|
+
"""
|
|
32
|
+
name = comp.get("name", "comparison")
|
|
33
|
+
p_value = comp.get("p_value")
|
|
34
|
+
stars = comp.get("stars") or (p_to_stars(p_value) if p_value else "")
|
|
35
|
+
method = comp.get("method", "")
|
|
36
|
+
effect_size = comp.get("effect_size")
|
|
37
|
+
|
|
38
|
+
if style == "brief":
|
|
39
|
+
if p_value is not None:
|
|
40
|
+
return f"{name}: {stars}"
|
|
41
|
+
return name
|
|
42
|
+
|
|
43
|
+
elif style == "detailed":
|
|
44
|
+
parts = [name]
|
|
45
|
+
if method:
|
|
46
|
+
parts.append(f"({method})")
|
|
47
|
+
if p_value is not None:
|
|
48
|
+
if p_value < 0.001:
|
|
49
|
+
parts.append("p<0.001")
|
|
50
|
+
else:
|
|
51
|
+
parts.append(f"p={p_value:.3f}")
|
|
52
|
+
if effect_size:
|
|
53
|
+
if isinstance(effect_size, dict):
|
|
54
|
+
es_name = effect_size.get("name", "d")
|
|
55
|
+
es_val = effect_size.get("value", 0)
|
|
56
|
+
parts.append(f"{es_name}={es_val:.2f}")
|
|
57
|
+
else:
|
|
58
|
+
parts.append(f"d={effect_size:.2f}")
|
|
59
|
+
return " ".join(parts)
|
|
60
|
+
|
|
61
|
+
else: # publication
|
|
62
|
+
if p_value is not None:
|
|
63
|
+
if p_value < 0.001:
|
|
64
|
+
p_str = "p<0.001"
|
|
65
|
+
else:
|
|
66
|
+
p_str = f"p={p_value:.3f}"
|
|
67
|
+
if effect_size:
|
|
68
|
+
if isinstance(effect_size, dict):
|
|
69
|
+
es_val = effect_size.get("value", 0)
|
|
70
|
+
else:
|
|
71
|
+
es_val = effect_size
|
|
72
|
+
return f"{name} ({p_str}, d={es_val:.2f})"
|
|
73
|
+
return f"{name} ({p_str})"
|
|
74
|
+
return name
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def format_panel_stats(stats: Dict[str, Any], style: str = "publication") -> str:
|
|
78
|
+
"""Format panel-level statistics.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
stats : dict
|
|
83
|
+
Panel stats dict with keys like: n, mean, std, sem, group.
|
|
84
|
+
style : str
|
|
85
|
+
"publication", "brief", or "detailed".
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
str
|
|
90
|
+
Formatted stats string.
|
|
91
|
+
"""
|
|
92
|
+
parts = []
|
|
93
|
+
|
|
94
|
+
group = stats.get("group")
|
|
95
|
+
if group:
|
|
96
|
+
parts.append(group)
|
|
97
|
+
|
|
98
|
+
n = stats.get("n")
|
|
99
|
+
if n is not None:
|
|
100
|
+
parts.append(f"n={n}")
|
|
101
|
+
|
|
102
|
+
mean = stats.get("mean")
|
|
103
|
+
std = stats.get("std")
|
|
104
|
+
sem = stats.get("sem")
|
|
105
|
+
|
|
106
|
+
if mean is not None:
|
|
107
|
+
if std is not None:
|
|
108
|
+
parts.append(f"mean={format_stats_value(mean)}±{format_stats_value(std)}")
|
|
109
|
+
elif sem is not None:
|
|
110
|
+
parts.append(
|
|
111
|
+
f"mean={format_stats_value(mean)}±{format_stats_value(sem)} SEM"
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
parts.append(f"mean={format_stats_value(mean)}")
|
|
115
|
+
|
|
116
|
+
return ", ".join(parts) if parts else ""
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def generate_figure_caption(
|
|
120
|
+
title: Optional[str] = None,
|
|
121
|
+
panel_captions: Optional[List[str]] = None,
|
|
122
|
+
stats: Optional[Dict[str, Any]] = None,
|
|
123
|
+
style: Literal["publication", "brief", "detailed"] = "publication",
|
|
124
|
+
template: Optional[str] = None,
|
|
125
|
+
) -> str:
|
|
126
|
+
"""Generate a figure caption from components.
|
|
127
|
+
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
title : str, optional
|
|
131
|
+
Figure title.
|
|
132
|
+
panel_captions : list of str, optional
|
|
133
|
+
List of panel captions.
|
|
134
|
+
stats : dict, optional
|
|
135
|
+
Figure-level stats with comparisons.
|
|
136
|
+
style : str
|
|
137
|
+
Caption style.
|
|
138
|
+
template : str, optional
|
|
139
|
+
Custom template with placeholders: {title}, {panels}, {stats}.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
str
|
|
144
|
+
Generated caption.
|
|
145
|
+
"""
|
|
146
|
+
# Build components
|
|
147
|
+
title_str = title or ""
|
|
148
|
+
|
|
149
|
+
# Panel descriptions
|
|
150
|
+
panels_str = ""
|
|
151
|
+
if panel_captions:
|
|
152
|
+
panels_str = " ".join(p for p in panel_captions if p)
|
|
153
|
+
|
|
154
|
+
# Stats summary
|
|
155
|
+
stats_str = ""
|
|
156
|
+
if stats:
|
|
157
|
+
comparisons = stats.get("comparisons", [])
|
|
158
|
+
if comparisons:
|
|
159
|
+
formatted = [format_comparison(c, style) for c in comparisons]
|
|
160
|
+
stats_str = "; ".join(formatted)
|
|
161
|
+
|
|
162
|
+
# Apply template
|
|
163
|
+
if template:
|
|
164
|
+
return template.format(
|
|
165
|
+
title=title_str,
|
|
166
|
+
panels=panels_str,
|
|
167
|
+
stats=stats_str,
|
|
168
|
+
).strip()
|
|
169
|
+
|
|
170
|
+
# Default formatting based on style
|
|
171
|
+
parts = []
|
|
172
|
+
if title_str:
|
|
173
|
+
parts.append(title_str + ".")
|
|
174
|
+
|
|
175
|
+
if panels_str:
|
|
176
|
+
parts.append(panels_str)
|
|
177
|
+
|
|
178
|
+
if stats_str:
|
|
179
|
+
parts.append(stats_str + ".")
|
|
180
|
+
|
|
181
|
+
return " ".join(parts).strip()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def generate_panel_caption(
|
|
185
|
+
label: Optional[str] = None,
|
|
186
|
+
stats: Optional[Dict[str, Any]] = None,
|
|
187
|
+
style: Literal["publication", "brief", "detailed"] = "publication",
|
|
188
|
+
) -> str:
|
|
189
|
+
"""Generate a panel caption from stats.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
label : str, optional
|
|
194
|
+
Panel label like "A" or "(A)".
|
|
195
|
+
stats : dict, optional
|
|
196
|
+
Panel-level stats.
|
|
197
|
+
style : str
|
|
198
|
+
Caption style.
|
|
199
|
+
|
|
200
|
+
Returns
|
|
201
|
+
-------
|
|
202
|
+
str
|
|
203
|
+
Generated panel caption.
|
|
204
|
+
"""
|
|
205
|
+
parts = []
|
|
206
|
+
|
|
207
|
+
if label:
|
|
208
|
+
# Ensure label is in parentheses
|
|
209
|
+
if not label.startswith("("):
|
|
210
|
+
label = f"({label})"
|
|
211
|
+
parts.append(label)
|
|
212
|
+
|
|
213
|
+
if stats:
|
|
214
|
+
stats_str = format_panel_stats(stats, style)
|
|
215
|
+
if stats_str:
|
|
216
|
+
parts.append(stats_str)
|
|
217
|
+
|
|
218
|
+
return " ".join(parts)
|
figrecipe/_wrappers/_figure.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"""Wrapped Figure that manages recording."""
|
|
4
4
|
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import TYPE_CHECKING, Any, List, Literal, Optional, Tuple, Union
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, Union
|
|
7
7
|
|
|
8
8
|
import matplotlib.pyplot as plt
|
|
9
9
|
import numpy as np
|
|
@@ -93,6 +93,22 @@ class RecordingFigure:
|
|
|
93
93
|
pass
|
|
94
94
|
return default
|
|
95
95
|
|
|
96
|
+
def _get_theme_text_color(self, default: str = "black") -> str:
|
|
97
|
+
"""Get text color from loaded style's theme settings."""
|
|
98
|
+
try:
|
|
99
|
+
from ..styles._style_loader import _STYLE_CACHE
|
|
100
|
+
|
|
101
|
+
if _STYLE_CACHE is not None:
|
|
102
|
+
theme = getattr(_STYLE_CACHE, "theme", None)
|
|
103
|
+
if theme is not None:
|
|
104
|
+
mode = getattr(theme, "mode", "light")
|
|
105
|
+
theme_colors = getattr(theme, mode, None)
|
|
106
|
+
if theme_colors is not None:
|
|
107
|
+
return getattr(theme_colors, "text", default)
|
|
108
|
+
except Exception:
|
|
109
|
+
pass
|
|
110
|
+
return default
|
|
111
|
+
|
|
96
112
|
def suptitle(self, t: str, **kwargs) -> Any:
|
|
97
113
|
"""Set super title for the figure and record it.
|
|
98
114
|
|
|
@@ -162,6 +178,156 @@ class RecordingFigure:
|
|
|
162
178
|
# Call the underlying figure's supylabel
|
|
163
179
|
return self._fig.supylabel(t, **kwargs)
|
|
164
180
|
|
|
181
|
+
def add_panel_labels(
|
|
182
|
+
self,
|
|
183
|
+
labels: Optional[List[str]] = None,
|
|
184
|
+
loc: str = "upper left",
|
|
185
|
+
offset: Tuple[float, float] = (-0.1, 1.05),
|
|
186
|
+
fontsize: Optional[float] = None,
|
|
187
|
+
fontweight: str = "bold",
|
|
188
|
+
**kwargs,
|
|
189
|
+
) -> List[Any]:
|
|
190
|
+
"""Add panel labels (A, B, C, D, etc.) to multi-panel figures.
|
|
191
|
+
|
|
192
|
+
Parameters
|
|
193
|
+
----------
|
|
194
|
+
labels : list of str, optional
|
|
195
|
+
Custom labels. If None, uses uppercase letters (A, B, C, ...).
|
|
196
|
+
loc : str
|
|
197
|
+
Location hint: 'upper left' (default), 'upper right', 'lower left', 'lower right'.
|
|
198
|
+
offset : tuple of float
|
|
199
|
+
(x, y) offset in axes coordinates from the corner.
|
|
200
|
+
Default is (-0.1, 1.05) for upper left positioning.
|
|
201
|
+
fontsize : float, optional
|
|
202
|
+
Font size in points. If None, uses style's title_pt or 10.
|
|
203
|
+
fontweight : str
|
|
204
|
+
Font weight (default: 'bold').
|
|
205
|
+
**kwargs
|
|
206
|
+
Additional arguments passed to ax.text().
|
|
207
|
+
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
list of Text
|
|
211
|
+
The matplotlib Text objects created.
|
|
212
|
+
|
|
213
|
+
Examples
|
|
214
|
+
--------
|
|
215
|
+
>>> fig, axes = fr.subplots(2, 2)
|
|
216
|
+
>>> fig.add_panel_labels() # Adds A, B, C, D
|
|
217
|
+
>>> fig.add_panel_labels(['i', 'ii', 'iii', 'iv']) # Custom labels
|
|
218
|
+
>>> fig.add_panel_labels(loc='upper right', offset=(1.05, 1.05))
|
|
219
|
+
"""
|
|
220
|
+
from ._panel_labels import add_panel_labels as _add_panel_labels
|
|
221
|
+
|
|
222
|
+
# Get fontsize from style if not specified
|
|
223
|
+
if fontsize is None:
|
|
224
|
+
fontsize = self._get_style_fontsize("title_pt", 10)
|
|
225
|
+
|
|
226
|
+
# Get theme text color (unless user provided 'color' in kwargs)
|
|
227
|
+
if "color" not in kwargs:
|
|
228
|
+
text_color = self._get_theme_text_color()
|
|
229
|
+
else:
|
|
230
|
+
text_color = kwargs.pop("color")
|
|
231
|
+
|
|
232
|
+
def record_callback(info):
|
|
233
|
+
self._recorder.figure_record.panel_labels = info
|
|
234
|
+
|
|
235
|
+
return _add_panel_labels(
|
|
236
|
+
all_axes=self.flat,
|
|
237
|
+
labels=labels,
|
|
238
|
+
loc=loc,
|
|
239
|
+
offset=offset,
|
|
240
|
+
fontsize=fontsize,
|
|
241
|
+
fontweight=fontweight,
|
|
242
|
+
text_color=text_color,
|
|
243
|
+
record_callback=record_callback,
|
|
244
|
+
**kwargs,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
def set_title_metadata(self, title: str) -> "RecordingFigure":
|
|
248
|
+
"""Set figure title metadata (not rendered, stored in recipe).
|
|
249
|
+
|
|
250
|
+
This is for storing a publication/reference title for the figure,
|
|
251
|
+
separate from suptitle which is rendered on the figure.
|
|
252
|
+
|
|
253
|
+
Parameters
|
|
254
|
+
----------
|
|
255
|
+
title : str
|
|
256
|
+
The figure title for publication/reference.
|
|
257
|
+
|
|
258
|
+
Returns
|
|
259
|
+
-------
|
|
260
|
+
RecordingFigure
|
|
261
|
+
Self for method chaining.
|
|
262
|
+
|
|
263
|
+
Examples
|
|
264
|
+
--------
|
|
265
|
+
>>> fig, ax = fr.subplots()
|
|
266
|
+
>>> fig.set_title_metadata("Effect of temperature on reaction rate")
|
|
267
|
+
>>> fig.set_caption("Figure 1. Reaction rates measured at various temperatures.")
|
|
268
|
+
"""
|
|
269
|
+
self._recorder.figure_record.title_metadata = title
|
|
270
|
+
return self
|
|
271
|
+
|
|
272
|
+
def set_caption(self, caption: str) -> "RecordingFigure":
|
|
273
|
+
"""Set figure caption metadata (not rendered, stored in recipe).
|
|
274
|
+
|
|
275
|
+
This is for storing a publication caption for the figure,
|
|
276
|
+
typically used in scientific papers (e.g., "Fig. 1. Description...").
|
|
277
|
+
|
|
278
|
+
Parameters
|
|
279
|
+
----------
|
|
280
|
+
caption : str
|
|
281
|
+
The figure caption text.
|
|
282
|
+
|
|
283
|
+
Returns
|
|
284
|
+
-------
|
|
285
|
+
RecordingFigure
|
|
286
|
+
Self for method chaining.
|
|
287
|
+
|
|
288
|
+
Examples
|
|
289
|
+
--------
|
|
290
|
+
>>> fig, ax = fr.subplots()
|
|
291
|
+
>>> fig.set_caption("Figure 1. Temperature dependence of reaction rates.")
|
|
292
|
+
"""
|
|
293
|
+
self._recorder.figure_record.caption = caption
|
|
294
|
+
return self
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def title_metadata(self) -> Optional[str]:
|
|
298
|
+
"""Get the figure title metadata."""
|
|
299
|
+
return self._recorder.figure_record.title_metadata
|
|
300
|
+
|
|
301
|
+
@property
|
|
302
|
+
def caption(self) -> Optional[str]:
|
|
303
|
+
"""Get the figure caption metadata."""
|
|
304
|
+
return self._recorder.figure_record.caption
|
|
305
|
+
|
|
306
|
+
def set_stats(self, stats: Dict[str, Any]) -> "RecordingFigure":
|
|
307
|
+
"""Set figure-level statistics metadata (not rendered, stored in recipe).
|
|
308
|
+
|
|
309
|
+
Parameters
|
|
310
|
+
----------
|
|
311
|
+
stats : dict
|
|
312
|
+
Statistics dictionary (comparisons, summary, correction_method, alpha).
|
|
313
|
+
"""
|
|
314
|
+
self._recorder.figure_record.stats = stats
|
|
315
|
+
return self
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def stats(self) -> Optional[Dict[str, Any]]:
|
|
319
|
+
"""Get the figure-level statistics metadata."""
|
|
320
|
+
return self._recorder.figure_record.stats
|
|
321
|
+
|
|
322
|
+
def generate_caption(self, style: str = "publication", template: str = None) -> str:
|
|
323
|
+
"""Generate caption from stored stats. Styles: publication, brief, detailed."""
|
|
324
|
+
from ._caption_generator import generate_figure_caption
|
|
325
|
+
|
|
326
|
+
panels = [ax.caption for ax in self.flat if ax.caption]
|
|
327
|
+
return generate_figure_caption(
|
|
328
|
+
self.title_metadata, panels, self.stats, style, template
|
|
329
|
+
)
|
|
330
|
+
|
|
165
331
|
def __getattr__(self, name: str) -> Any:
|
|
166
332
|
"""Delegate attribute access to underlying figure."""
|
|
167
333
|
return getattr(self._fig, name)
|
|
@@ -203,6 +369,15 @@ class RecordingFigure:
|
|
|
203
369
|
>>> fig.savefig('figure.png') # Saves both figure.png and figure.yaml
|
|
204
370
|
>>> fig.savefig('figure.png', save_recipe=False) # Image only
|
|
205
371
|
"""
|
|
372
|
+
# Finalize ticks and special plots before saving
|
|
373
|
+
from ..styles._style_applier import finalize_special_plots, finalize_ticks
|
|
374
|
+
from ..styles._style_loader import get_current_style_dict
|
|
375
|
+
|
|
376
|
+
style_dict = get_current_style_dict()
|
|
377
|
+
for ax in self._fig.get_axes():
|
|
378
|
+
finalize_ticks(ax)
|
|
379
|
+
finalize_special_plots(ax, style_dict)
|
|
380
|
+
|
|
206
381
|
# Handle file-like objects (BytesIO, etc.) - just pass through
|
|
207
382
|
if hasattr(fname, "write"):
|
|
208
383
|
self._fig.savefig(fname, **kwargs)
|
|
@@ -255,6 +430,7 @@ def create_recording_subplots(
|
|
|
255
430
|
nrows: int = 1,
|
|
256
431
|
ncols: int = 1,
|
|
257
432
|
recorder: Optional["Recorder"] = None,
|
|
433
|
+
panel_labels: bool = False,
|
|
258
434
|
**kwargs,
|
|
259
435
|
) -> Tuple[RecordingFigure, Union[RecordingAxes, NDArray]]:
|
|
260
436
|
"""Create a figure with recording-enabled axes.
|
|
@@ -267,6 +443,9 @@ def create_recording_subplots(
|
|
|
267
443
|
Number of columns.
|
|
268
444
|
recorder : Recorder, optional
|
|
269
445
|
Recorder instance. Created if not provided.
|
|
446
|
+
panel_labels : bool
|
|
447
|
+
If True and figure has multiple panels, automatically add
|
|
448
|
+
panel labels (A, B, C, D, ...). Default is False.
|
|
270
449
|
**kwargs
|
|
271
450
|
Passed to plt.subplots().
|
|
272
451
|
|
|
@@ -276,6 +455,10 @@ def create_recording_subplots(
|
|
|
276
455
|
Wrapped figure.
|
|
277
456
|
axes : RecordingAxes or ndarray
|
|
278
457
|
Wrapped axes (single if 1x1, otherwise numpy array matching matplotlib).
|
|
458
|
+
|
|
459
|
+
Examples
|
|
460
|
+
--------
|
|
461
|
+
>>> fig, axes = fr.subplots(2, 2, panel_labels=True) # Auto-adds A, B, C, D
|
|
279
462
|
"""
|
|
280
463
|
from .._recorder import Recorder
|
|
281
464
|
|
|
@@ -312,6 +495,10 @@ def create_recording_subplots(
|
|
|
312
495
|
|
|
313
496
|
wrapped_fig = RecordingFigure(fig, recorder, wrapped_axes)
|
|
314
497
|
|
|
498
|
+
# Add panel labels if requested (multi-panel figures only)
|
|
499
|
+
if panel_labels:
|
|
500
|
+
wrapped_fig.add_panel_labels()
|
|
501
|
+
|
|
315
502
|
# Return in same shape as matplotlib (numpy arrays for consistency)
|
|
316
503
|
if nrows == 1:
|
|
317
504
|
# 1xN -> 1D array of shape (N,)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Panel label utilities for multi-panel figures."""
|
|
4
|
+
|
|
5
|
+
import string
|
|
6
|
+
from typing import TYPE_CHECKING, Any, List, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ._axes import RecordingAxes
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def add_panel_labels(
|
|
13
|
+
all_axes: List["RecordingAxes"],
|
|
14
|
+
labels: Optional[List[str]],
|
|
15
|
+
loc: str,
|
|
16
|
+
offset: Tuple[float, float],
|
|
17
|
+
fontsize: float,
|
|
18
|
+
fontweight: str,
|
|
19
|
+
text_color: str,
|
|
20
|
+
record_callback: Any,
|
|
21
|
+
**kwargs,
|
|
22
|
+
) -> List[Any]:
|
|
23
|
+
"""Add panel labels (A, B, C, D, etc.) to axes.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
all_axes : list of RecordingAxes
|
|
28
|
+
Flattened list of all axes.
|
|
29
|
+
labels : list of str or None
|
|
30
|
+
Custom labels. If None, uses uppercase letters.
|
|
31
|
+
loc : str
|
|
32
|
+
Location hint: 'upper left', 'upper right', 'lower left', 'lower right'.
|
|
33
|
+
offset : tuple of float
|
|
34
|
+
(x, y) offset in axes coordinates.
|
|
35
|
+
fontsize : float
|
|
36
|
+
Font size in points.
|
|
37
|
+
fontweight : str
|
|
38
|
+
Font weight.
|
|
39
|
+
text_color : str
|
|
40
|
+
Text color.
|
|
41
|
+
record_callback : callable
|
|
42
|
+
Callback to record panel labels info.
|
|
43
|
+
**kwargs
|
|
44
|
+
Additional arguments passed to ax.text().
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
list of Text
|
|
49
|
+
The matplotlib Text objects created.
|
|
50
|
+
"""
|
|
51
|
+
n_axes = len(all_axes)
|
|
52
|
+
|
|
53
|
+
# Generate default labels if not provided
|
|
54
|
+
if labels is None:
|
|
55
|
+
labels = list(string.ascii_uppercase[:n_axes])
|
|
56
|
+
elif len(labels) < n_axes:
|
|
57
|
+
# Extend with letters if not enough labels provided
|
|
58
|
+
labels = list(labels) + list(string.ascii_uppercase[len(labels) : n_axes])
|
|
59
|
+
|
|
60
|
+
# Calculate position based on loc
|
|
61
|
+
x, y, ha, va = _calculate_position(loc, offset)
|
|
62
|
+
|
|
63
|
+
# Record panel labels
|
|
64
|
+
record_callback(
|
|
65
|
+
{
|
|
66
|
+
"labels": labels[:n_axes],
|
|
67
|
+
"loc": loc,
|
|
68
|
+
"offset": offset,
|
|
69
|
+
"fontsize": fontsize,
|
|
70
|
+
"fontweight": fontweight,
|
|
71
|
+
"color": text_color,
|
|
72
|
+
"kwargs": kwargs,
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Add labels to each axes
|
|
77
|
+
text_objects = []
|
|
78
|
+
for ax, label in zip(all_axes, labels[:n_axes]):
|
|
79
|
+
text = ax.ax.text(
|
|
80
|
+
x,
|
|
81
|
+
y,
|
|
82
|
+
label,
|
|
83
|
+
transform=ax.ax.transAxes,
|
|
84
|
+
fontsize=fontsize,
|
|
85
|
+
fontweight=fontweight,
|
|
86
|
+
color=text_color,
|
|
87
|
+
ha=ha,
|
|
88
|
+
va=va,
|
|
89
|
+
**kwargs,
|
|
90
|
+
)
|
|
91
|
+
text_objects.append(text)
|
|
92
|
+
|
|
93
|
+
return text_objects
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _calculate_position(
|
|
97
|
+
loc: str, offset: Tuple[float, float]
|
|
98
|
+
) -> Tuple[float, float, str, str]:
|
|
99
|
+
"""Calculate text position and alignment based on location.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
tuple
|
|
104
|
+
(x, y, ha, va) where ha/va are horizontal/vertical alignment.
|
|
105
|
+
"""
|
|
106
|
+
if loc == "upper left":
|
|
107
|
+
x, y = offset
|
|
108
|
+
ha, va = "right", "bottom"
|
|
109
|
+
elif loc == "upper right":
|
|
110
|
+
x, y = offset
|
|
111
|
+
ha, va = "left", "bottom"
|
|
112
|
+
elif loc == "lower left":
|
|
113
|
+
x, y = offset[0], -offset[1] + 1.0
|
|
114
|
+
ha, va = "right", "top"
|
|
115
|
+
elif loc == "lower right":
|
|
116
|
+
x, y = offset
|
|
117
|
+
ha, va = "left", "top"
|
|
118
|
+
else:
|
|
119
|
+
x, y = offset
|
|
120
|
+
ha, va = "right", "bottom"
|
|
121
|
+
|
|
122
|
+
return x, y, ha, va
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
__all__ = ["add_panel_labels"]
|
|
126
|
+
|
|
127
|
+
# EOF
|