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,29 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""csd: cross spectral density demo."""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def plot_csd(plt, rng, ax=None):
|
|
9
|
+
"""Cross spectral density demo.
|
|
10
|
+
|
|
11
|
+
Demonstrates: ax.csd()
|
|
12
|
+
"""
|
|
13
|
+
if ax is None:
|
|
14
|
+
fig, ax = plt.subplots()
|
|
15
|
+
else:
|
|
16
|
+
fig = ax.get_figure() if hasattr(ax, "get_figure") else ax.fig
|
|
17
|
+
|
|
18
|
+
fs = 1000
|
|
19
|
+
t = np.linspace(0, 1, fs)
|
|
20
|
+
x = np.sin(2 * np.pi * 50 * t) + rng.normal(0, 0.3, len(t))
|
|
21
|
+
y = np.sin(2 * np.pi * 50 * t + np.pi / 4) + rng.normal(0, 0.3, len(t))
|
|
22
|
+
ax.csd(x, y, Fs=fs, id="csd")
|
|
23
|
+
ax.set_xlabel("Frequency")
|
|
24
|
+
ax.set_ylabel("CSD")
|
|
25
|
+
ax.set_title("csd")
|
|
26
|
+
return fig, ax
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# EOF
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""magnitude_spectrum: magnitude spectrum demo."""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def plot_magnitude_spectrum(plt, rng, ax=None):
|
|
9
|
+
"""Magnitude spectrum demo.
|
|
10
|
+
|
|
11
|
+
Demonstrates: ax.magnitude_spectrum()
|
|
12
|
+
"""
|
|
13
|
+
if ax is None:
|
|
14
|
+
fig, ax = plt.subplots()
|
|
15
|
+
else:
|
|
16
|
+
fig = ax.get_figure() if hasattr(ax, "get_figure") else ax.fig
|
|
17
|
+
|
|
18
|
+
fs = 1000
|
|
19
|
+
t = np.linspace(0, 1, fs)
|
|
20
|
+
signal = np.sin(2 * np.pi * 50 * t) + 0.5 * np.sin(2 * np.pi * 120 * t)
|
|
21
|
+
ax.magnitude_spectrum(signal, Fs=fs, id="magnitude_spectrum")
|
|
22
|
+
ax.set_xlabel("Frequency")
|
|
23
|
+
ax.set_ylabel("Magnitude")
|
|
24
|
+
ax.set_title("magnitude_spectrum")
|
|
25
|
+
return fig, ax
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# EOF
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""phase_spectrum: phase spectrum demo."""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def plot_phase_spectrum(plt, rng, ax=None):
|
|
9
|
+
"""Phase spectrum demo.
|
|
10
|
+
|
|
11
|
+
Demonstrates: ax.phase_spectrum()
|
|
12
|
+
"""
|
|
13
|
+
if ax is None:
|
|
14
|
+
fig, ax = plt.subplots()
|
|
15
|
+
else:
|
|
16
|
+
fig = ax.get_figure() if hasattr(ax, "get_figure") else ax.fig
|
|
17
|
+
|
|
18
|
+
fs = 1000
|
|
19
|
+
t = np.linspace(0, 1, fs)
|
|
20
|
+
signal = np.sin(2 * np.pi * 50 * t) + 0.5 * np.sin(2 * np.pi * 120 * t + np.pi / 3)
|
|
21
|
+
ax.phase_spectrum(signal, Fs=fs, id="phase_spectrum")
|
|
22
|
+
ax.set_xlabel("Frequency")
|
|
23
|
+
ax.set_ylabel("Phase (radians)")
|
|
24
|
+
ax.set_title("phase_spectrum")
|
|
25
|
+
return fig, ax
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# EOF
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""psd: power spectral density demo."""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def plot_psd(plt, rng, ax=None):
|
|
9
|
+
"""Power spectral density demo.
|
|
10
|
+
|
|
11
|
+
Demonstrates: ax.psd()
|
|
12
|
+
"""
|
|
13
|
+
if ax is None:
|
|
14
|
+
fig, ax = plt.subplots()
|
|
15
|
+
else:
|
|
16
|
+
fig = ax.get_figure() if hasattr(ax, "get_figure") else ax.fig
|
|
17
|
+
|
|
18
|
+
fs = 1000
|
|
19
|
+
t = np.linspace(0, 1, fs)
|
|
20
|
+
signal = np.sin(2 * np.pi * 50 * t) + 0.5 * np.sin(2 * np.pi * 120 * t)
|
|
21
|
+
signal += rng.normal(0, 0.3, len(t))
|
|
22
|
+
ax.psd(signal, Fs=fs, id="psd")
|
|
23
|
+
ax.set_xlabel("Frequency")
|
|
24
|
+
ax.set_ylabel("Power/Frequency (dB/Hz)")
|
|
25
|
+
ax.set_title("psd")
|
|
26
|
+
return fig, ax
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# EOF
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""specgram: spectrogram demo."""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def plot_specgram(plt, rng, ax=None):
|
|
9
|
+
"""Spectrogram demo.
|
|
10
|
+
|
|
11
|
+
Demonstrates: ax.specgram()
|
|
12
|
+
"""
|
|
13
|
+
if ax is None:
|
|
14
|
+
fig, ax = plt.subplots()
|
|
15
|
+
else:
|
|
16
|
+
fig = ax.get_figure() if hasattr(ax, "get_figure") else ax.fig
|
|
17
|
+
|
|
18
|
+
# Create signal with varying frequency
|
|
19
|
+
fs = 1000
|
|
20
|
+
t = np.linspace(0, 2, 2000)
|
|
21
|
+
freq = 50 + 50 * t # Chirp signal
|
|
22
|
+
signal = np.sin(2 * np.pi * freq * t)
|
|
23
|
+
ax.specgram(signal, Fs=fs, id="specgram")
|
|
24
|
+
ax.set_xlabel("Time")
|
|
25
|
+
ax.set_ylabel("Frequency")
|
|
26
|
+
ax.set_title("specgram")
|
|
27
|
+
return fig, ax
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# EOF
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""xcorr: cross-correlation demo."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def plot_xcorr(plt, rng, ax=None):
|
|
7
|
+
"""Cross-correlation demo.
|
|
8
|
+
|
|
9
|
+
Demonstrates: ax.xcorr()
|
|
10
|
+
"""
|
|
11
|
+
if ax is None:
|
|
12
|
+
fig, ax = plt.subplots()
|
|
13
|
+
else:
|
|
14
|
+
fig = ax.get_figure() if hasattr(ax, "get_figure") else ax.fig
|
|
15
|
+
|
|
16
|
+
x = rng.normal(0, 1, 100)
|
|
17
|
+
y = rng.normal(0, 1, 100)
|
|
18
|
+
ax.xcorr(x, y, maxlags=50, id="xcorr")
|
|
19
|
+
ax.set_xlabel("Lag")
|
|
20
|
+
ax.set_ylabel("Cross-correlation")
|
|
21
|
+
ax.set_title("xcorr")
|
|
22
|
+
return fig, ax
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# EOF
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""barbs: wind barbs demo."""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def plot_barbs(plt, rng, ax=None):
|
|
9
|
+
"""Wind barbs demo.
|
|
10
|
+
|
|
11
|
+
Demonstrates: ax.barbs()
|
|
12
|
+
"""
|
|
13
|
+
if ax is None:
|
|
14
|
+
fig, ax = plt.subplots()
|
|
15
|
+
else:
|
|
16
|
+
fig = ax.get_figure() if hasattr(ax, "get_figure") else ax.fig
|
|
17
|
+
|
|
18
|
+
x = np.arange(0, 5, 1)
|
|
19
|
+
y = np.arange(0, 5, 1)
|
|
20
|
+
X, Y = np.meshgrid(x, y)
|
|
21
|
+
U = rng.uniform(-10, 10, X.shape)
|
|
22
|
+
V = rng.uniform(-10, 10, Y.shape)
|
|
23
|
+
ax.barbs(X, Y, U, V, id="barbs")
|
|
24
|
+
ax.set_xlabel("X")
|
|
25
|
+
ax.set_ylabel("Y")
|
|
26
|
+
ax.set_title("barbs")
|
|
27
|
+
return fig, ax
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# EOF
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""quiver: vector field demo."""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def plot_quiver(plt, rng, ax=None):
|
|
9
|
+
"""Vector field demo.
|
|
10
|
+
|
|
11
|
+
Demonstrates: ax.quiver()
|
|
12
|
+
"""
|
|
13
|
+
if ax is None:
|
|
14
|
+
fig, ax = plt.subplots()
|
|
15
|
+
else:
|
|
16
|
+
fig = ax.get_figure() if hasattr(ax, "get_figure") else ax.fig
|
|
17
|
+
|
|
18
|
+
x = np.arange(0, 10, 1)
|
|
19
|
+
y = np.arange(0, 10, 1)
|
|
20
|
+
X, Y = np.meshgrid(x, y)
|
|
21
|
+
U = np.cos(X * 0.5)
|
|
22
|
+
V = np.sin(Y * 0.5)
|
|
23
|
+
ax.quiver(X, Y, U, V, id="quiver")
|
|
24
|
+
ax.set_xlabel("X")
|
|
25
|
+
ax.set_ylabel("Y")
|
|
26
|
+
ax.set_title("quiver")
|
|
27
|
+
return fig, ax
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# EOF
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""streamplot: streamline plot demo."""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def plot_streamplot(plt, rng, ax=None):
|
|
9
|
+
"""Streamline plot demo.
|
|
10
|
+
|
|
11
|
+
Demonstrates: ax.streamplot()
|
|
12
|
+
"""
|
|
13
|
+
if ax is None:
|
|
14
|
+
fig, ax = plt.subplots()
|
|
15
|
+
else:
|
|
16
|
+
fig = ax.get_figure() if hasattr(ax, "get_figure") else ax.fig
|
|
17
|
+
|
|
18
|
+
x = np.linspace(-3, 3, 30)
|
|
19
|
+
y = np.linspace(-3, 3, 30)
|
|
20
|
+
X, Y = np.meshgrid(x, y)
|
|
21
|
+
U = -Y
|
|
22
|
+
V = X
|
|
23
|
+
ax.streamplot(X, Y, U, V, id="streamplot")
|
|
24
|
+
ax.set_xlabel("X")
|
|
25
|
+
ax.set_ylabel("Y")
|
|
26
|
+
ax.set_title("streamplot")
|
|
27
|
+
return fig, ax
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# EOF
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
figrecipe GUI Editor - Interactive figure styling with hitmap-based element selection.
|
|
5
|
+
|
|
6
|
+
This module provides a Flask-based web editor for interactively adjusting
|
|
7
|
+
figure styles. It supports both live RecordingFigure objects and saved
|
|
8
|
+
recipe files (.yaml).
|
|
9
|
+
|
|
10
|
+
Usage
|
|
11
|
+
-----
|
|
12
|
+
>>> import figrecipe as fr
|
|
13
|
+
>>> fig, ax = fr.subplots()
|
|
14
|
+
>>> ax.plot(x, y, id='data')
|
|
15
|
+
>>> fr.edit(fig) # Opens browser with interactive editor
|
|
16
|
+
|
|
17
|
+
>>> # Or from saved recipe
|
|
18
|
+
>>> fr.edit('recipe.yaml')
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Dict, Optional, Union
|
|
23
|
+
|
|
24
|
+
from .._wrappers import RecordingFigure
|
|
25
|
+
from ._flask_app import FigureEditor
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def edit(
|
|
29
|
+
source: Optional[Union[RecordingFigure, str, Path]] = None,
|
|
30
|
+
style: Optional[Union[str, Dict[str, Any]]] = None,
|
|
31
|
+
port: int = 5050,
|
|
32
|
+
open_browser: bool = True,
|
|
33
|
+
hot_reload: bool = False,
|
|
34
|
+
working_dir: Optional[Union[str, Path]] = None,
|
|
35
|
+
) -> Dict[str, Any]:
|
|
36
|
+
"""
|
|
37
|
+
Launch interactive GUI editor for figure styling.
|
|
38
|
+
|
|
39
|
+
Opens a browser-based editor that allows interactive adjustment of
|
|
40
|
+
figure styles using hitmap-based element selection.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
source : RecordingFigure, str, Path, or None
|
|
45
|
+
Either a live RecordingFigure object, path to a .yaml/.png file,
|
|
46
|
+
or None to create a new blank figure.
|
|
47
|
+
style : str or dict, optional
|
|
48
|
+
Style preset name (e.g., 'SCITEX', 'SCITEX_DARK') or style dict.
|
|
49
|
+
If None, uses the currently loaded global style.
|
|
50
|
+
port : int, optional
|
|
51
|
+
Flask server port (default: 5050). Auto-finds available port if occupied.
|
|
52
|
+
open_browser : bool, optional
|
|
53
|
+
Whether to open browser automatically (default: True).
|
|
54
|
+
hot_reload : bool, optional
|
|
55
|
+
Enable hot reload - server restarts when source files change (default: False).
|
|
56
|
+
Like Django's development server. Browser auto-refreshes on reconnect.
|
|
57
|
+
working_dir : str or Path, optional
|
|
58
|
+
Working directory for file switching feature (default: current directory).
|
|
59
|
+
The file switcher will list recipe files from this directory.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
dict
|
|
64
|
+
Final style overrides after editing session.
|
|
65
|
+
|
|
66
|
+
Raises
|
|
67
|
+
------
|
|
68
|
+
ImportError
|
|
69
|
+
If Flask is not installed.
|
|
70
|
+
TypeError
|
|
71
|
+
If source is neither RecordingFigure nor valid path.
|
|
72
|
+
FileNotFoundError
|
|
73
|
+
If recipe file path does not exist.
|
|
74
|
+
|
|
75
|
+
Examples
|
|
76
|
+
--------
|
|
77
|
+
Edit a live figure:
|
|
78
|
+
|
|
79
|
+
>>> import figrecipe as fr
|
|
80
|
+
>>> fig, ax = fr.subplots()
|
|
81
|
+
>>> ax.plot([1, 2, 3], [1, 4, 9], id='quadratic')
|
|
82
|
+
>>> overrides = fr.edit(fig)
|
|
83
|
+
|
|
84
|
+
Edit a saved recipe:
|
|
85
|
+
|
|
86
|
+
>>> overrides = fr.edit('my_figure.yaml')
|
|
87
|
+
|
|
88
|
+
With explicit style:
|
|
89
|
+
|
|
90
|
+
>>> overrides = fr.edit(fig, style='SCITEX_DARK')
|
|
91
|
+
"""
|
|
92
|
+
import importlib.util
|
|
93
|
+
|
|
94
|
+
if importlib.util.find_spec("flask") is None:
|
|
95
|
+
raise ImportError(
|
|
96
|
+
"Flask is required for the GUI editor. "
|
|
97
|
+
"Install with: pip install figrecipe[editor] or pip install flask"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
import tempfile
|
|
101
|
+
|
|
102
|
+
from ._flask_app import FigureEditor
|
|
103
|
+
from ._hitmap import generate_hitmap, hitmap_to_base64
|
|
104
|
+
|
|
105
|
+
# Handle different input types
|
|
106
|
+
fig, recipe_path = _resolve_source(source)
|
|
107
|
+
|
|
108
|
+
# Load style if string preset name provided
|
|
109
|
+
style_dict = _resolve_style(style)
|
|
110
|
+
|
|
111
|
+
# Save static PNG FIRST - this is the source of truth for initial display
|
|
112
|
+
mpl_fig = fig.fig if hasattr(fig, "fig") else fig
|
|
113
|
+
static_png_path = Path(tempfile.mktemp(suffix="_figrecipe_static.png"))
|
|
114
|
+
mpl_fig.savefig(static_png_path, format="png", dpi=150, bbox_inches="tight")
|
|
115
|
+
|
|
116
|
+
# Generate hitmap ONCE at this point
|
|
117
|
+
# Pass RecordingFigure to preserve record for plot type detection
|
|
118
|
+
hitmap, color_map = generate_hitmap(fig)
|
|
119
|
+
hitmap_base64 = hitmap_to_base64(hitmap)
|
|
120
|
+
|
|
121
|
+
# Resolve working directory
|
|
122
|
+
resolved_working_dir = Path(working_dir) if working_dir else Path.cwd()
|
|
123
|
+
|
|
124
|
+
# Create and run editor with pre-rendered static PNG
|
|
125
|
+
editor = FigureEditor(
|
|
126
|
+
fig=fig,
|
|
127
|
+
recipe_path=recipe_path,
|
|
128
|
+
style=style_dict,
|
|
129
|
+
port=port,
|
|
130
|
+
static_png_path=static_png_path,
|
|
131
|
+
hitmap_base64=hitmap_base64,
|
|
132
|
+
color_map=color_map,
|
|
133
|
+
hot_reload=hot_reload,
|
|
134
|
+
working_dir=resolved_working_dir,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return editor.run(open_browser=open_browser)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _resolve_source(source: Optional[Union[RecordingFigure, str, Path]]):
|
|
141
|
+
"""
|
|
142
|
+
Resolve source to figure and optional recipe path.
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
source : RecordingFigure, str, Path, or None
|
|
147
|
+
Input source. If None, creates a new blank figure.
|
|
148
|
+
If PNG path, tries to find associated YAML recipe.
|
|
149
|
+
|
|
150
|
+
Returns
|
|
151
|
+
-------
|
|
152
|
+
tuple
|
|
153
|
+
(RecordingFigure, Path or None)
|
|
154
|
+
"""
|
|
155
|
+
# Handle None - create new blank figure
|
|
156
|
+
if source is None:
|
|
157
|
+
from .. import subplots
|
|
158
|
+
|
|
159
|
+
fig, ax = subplots()
|
|
160
|
+
ax.set_title("New Figure")
|
|
161
|
+
ax.text(
|
|
162
|
+
0.5,
|
|
163
|
+
0.5,
|
|
164
|
+
"Add plots using fr.edit(fig)",
|
|
165
|
+
ha="center",
|
|
166
|
+
va="center",
|
|
167
|
+
transform=ax.transAxes,
|
|
168
|
+
fontsize=12,
|
|
169
|
+
color="gray",
|
|
170
|
+
)
|
|
171
|
+
return fig, None
|
|
172
|
+
|
|
173
|
+
if isinstance(source, RecordingFigure):
|
|
174
|
+
return source, None
|
|
175
|
+
|
|
176
|
+
# Handle matplotlib Figure (e.g., from reproduce())
|
|
177
|
+
from matplotlib.figure import Figure
|
|
178
|
+
|
|
179
|
+
if isinstance(source, Figure):
|
|
180
|
+
from .._recorder import FigureRecord, Recorder
|
|
181
|
+
from .._wrappers._figure import RecordingFigure as RF
|
|
182
|
+
|
|
183
|
+
wrapped_fig = RF.__new__(RF)
|
|
184
|
+
wrapped_fig._fig = source
|
|
185
|
+
wrapped_fig._axes = [[ax] for ax in source.axes] # 2D list format
|
|
186
|
+
wrapped_fig._recorder = Recorder()
|
|
187
|
+
wrapped_fig._recorder._figure_record = FigureRecord(
|
|
188
|
+
figsize=tuple(source.get_size_inches()),
|
|
189
|
+
dpi=int(source.dpi),
|
|
190
|
+
)
|
|
191
|
+
return wrapped_fig, None
|
|
192
|
+
|
|
193
|
+
# Assume it's a path
|
|
194
|
+
path = Path(source)
|
|
195
|
+
if not path.exists():
|
|
196
|
+
raise FileNotFoundError(f"File not found: {path}")
|
|
197
|
+
|
|
198
|
+
# Handle PNG path - find associated YAML
|
|
199
|
+
if path.suffix.lower() == ".png":
|
|
200
|
+
yaml_path = path.with_suffix(".yaml")
|
|
201
|
+
if yaml_path.exists():
|
|
202
|
+
path = yaml_path
|
|
203
|
+
else:
|
|
204
|
+
yml_path = path.with_suffix(".yml")
|
|
205
|
+
if yml_path.exists():
|
|
206
|
+
path = yml_path
|
|
207
|
+
else:
|
|
208
|
+
raise FileNotFoundError(
|
|
209
|
+
f"No recipe found for {path.name}. "
|
|
210
|
+
f"Expected {yaml_path.name} or {yml_path.name}"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if path.suffix.lower() not in (".yaml", ".yml"):
|
|
214
|
+
raise ValueError(f"Expected .yaml, .yml, or .png file, got: {path.suffix}")
|
|
215
|
+
|
|
216
|
+
# Load recipe and reproduce figure
|
|
217
|
+
from .._reproducer import reproduce
|
|
218
|
+
|
|
219
|
+
fig, axes = reproduce(path)
|
|
220
|
+
|
|
221
|
+
# Wrap in RecordingFigure if needed
|
|
222
|
+
if not isinstance(fig, RecordingFigure):
|
|
223
|
+
from .._wrappers._figure import RecordingFigure as RF
|
|
224
|
+
|
|
225
|
+
# Create a minimal wrapper
|
|
226
|
+
wrapped_fig = RF.__new__(RF)
|
|
227
|
+
wrapped_fig.fig = fig
|
|
228
|
+
wrapped_fig._axes = axes if isinstance(axes, list) else [axes]
|
|
229
|
+
from .._recorder import FigureRecord
|
|
230
|
+
|
|
231
|
+
wrapped_fig.record = FigureRecord(
|
|
232
|
+
figsize=fig.get_size_inches().tolist(),
|
|
233
|
+
dpi=fig.dpi,
|
|
234
|
+
)
|
|
235
|
+
fig = wrapped_fig
|
|
236
|
+
|
|
237
|
+
return fig, path
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _resolve_style(
|
|
241
|
+
style: Optional[Union[str, Dict[str, Any]]],
|
|
242
|
+
) -> Optional[Dict[str, Any]]:
|
|
243
|
+
"""
|
|
244
|
+
Resolve style to dictionary.
|
|
245
|
+
|
|
246
|
+
Parameters
|
|
247
|
+
----------
|
|
248
|
+
style : str, dict, or None
|
|
249
|
+
Style preset name or dict.
|
|
250
|
+
|
|
251
|
+
Returns
|
|
252
|
+
-------
|
|
253
|
+
dict or None
|
|
254
|
+
Style dictionary.
|
|
255
|
+
"""
|
|
256
|
+
if style is None:
|
|
257
|
+
# Use global style if loaded
|
|
258
|
+
from ..styles._style_loader import _STYLE_CACHE
|
|
259
|
+
|
|
260
|
+
if _STYLE_CACHE is not None:
|
|
261
|
+
from ..styles import to_subplots_kwargs
|
|
262
|
+
|
|
263
|
+
return to_subplots_kwargs(_STYLE_CACHE)
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
if isinstance(style, dict):
|
|
267
|
+
return style
|
|
268
|
+
|
|
269
|
+
if isinstance(style, str):
|
|
270
|
+
from ..styles import load_style, to_subplots_kwargs
|
|
271
|
+
|
|
272
|
+
loaded = load_style(style)
|
|
273
|
+
return to_subplots_kwargs(loaded) if loaded else None
|
|
274
|
+
|
|
275
|
+
raise TypeError(f"style must be str, dict, or None, got {type(style)}")
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
__all__ = ["edit", "FigureEditor"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Modular bbox extraction for figure elements.
|
|
5
|
+
|
|
6
|
+
This package provides functions for extracting bounding boxes from
|
|
7
|
+
matplotlib figure elements for hit detection in the GUI editor.
|
|
8
|
+
|
|
9
|
+
The main function `extract_bboxes` is re-exported from _bbox_main.py
|
|
10
|
+
for backward compatibility.
|
|
11
|
+
|
|
12
|
+
Modules:
|
|
13
|
+
- _transforms: Coordinate transformation utilities
|
|
14
|
+
- _elements: General element, text, and tick bbox extraction
|
|
15
|
+
- _lines: Line and quiver bbox extraction
|
|
16
|
+
- _collections: Collection (scatter, fill) and patch bbox extraction
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# Re-export main function from _extract.py
|
|
20
|
+
# Import modular helpers
|
|
21
|
+
from ._collections import get_collection_bbox, get_patch_bbox
|
|
22
|
+
from ._elements import get_element_bbox, get_text_bbox, get_tick_labels_bbox
|
|
23
|
+
from ._extract import extract_bboxes
|
|
24
|
+
from ._lines import get_line_bbox, get_quiver_bbox
|
|
25
|
+
from ._transforms import display_to_image, transform_bbox
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
# Main function
|
|
29
|
+
"extract_bboxes",
|
|
30
|
+
# Transforms
|
|
31
|
+
"transform_bbox",
|
|
32
|
+
"display_to_image",
|
|
33
|
+
# Elements
|
|
34
|
+
"get_element_bbox",
|
|
35
|
+
"get_text_bbox",
|
|
36
|
+
"get_tick_labels_bbox",
|
|
37
|
+
# Lines
|
|
38
|
+
"get_line_bbox",
|
|
39
|
+
"get_quiver_bbox",
|
|
40
|
+
# Collections
|
|
41
|
+
"get_collection_bbox",
|
|
42
|
+
"get_patch_bbox",
|
|
43
|
+
]
|