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,223 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Style and theme Flask route handlers for the figure editor.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from flask import jsonify, request
|
|
8
|
+
|
|
9
|
+
from ._helpers import get_form_values_from_style, render_with_overrides
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register_style_routes(app, editor):
|
|
13
|
+
"""Register style/theme routes with the Flask app."""
|
|
14
|
+
from ._overrides import save_overrides
|
|
15
|
+
|
|
16
|
+
@app.route("/style")
|
|
17
|
+
def get_style():
|
|
18
|
+
"""Get current style configuration."""
|
|
19
|
+
return jsonify(
|
|
20
|
+
{
|
|
21
|
+
"base_style": editor.style_overrides.base_style,
|
|
22
|
+
"programmatic_style": editor.style_overrides.programmatic_style,
|
|
23
|
+
"manual_overrides": editor.style_overrides.manual_overrides,
|
|
24
|
+
"effective_style": editor.get_effective_style(),
|
|
25
|
+
"has_overrides": editor.style_overrides.has_manual_overrides(),
|
|
26
|
+
"manual_timestamp": editor.style_overrides.manual_timestamp,
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
@app.route("/theme")
|
|
31
|
+
def get_theme():
|
|
32
|
+
"""Get current theme YAML content for display."""
|
|
33
|
+
import io as yaml_io
|
|
34
|
+
|
|
35
|
+
from ruamel.yaml import YAML
|
|
36
|
+
|
|
37
|
+
style = editor.get_effective_style()
|
|
38
|
+
style_name = style.get("_name", "SCITEX")
|
|
39
|
+
|
|
40
|
+
yaml = YAML()
|
|
41
|
+
yaml.default_flow_style = False
|
|
42
|
+
yaml.indent(mapping=2, sequence=4, offset=2)
|
|
43
|
+
stream = yaml_io.StringIO()
|
|
44
|
+
yaml.dump(style, stream)
|
|
45
|
+
yaml_content = stream.getvalue()
|
|
46
|
+
|
|
47
|
+
return jsonify(
|
|
48
|
+
{
|
|
49
|
+
"name": style_name,
|
|
50
|
+
"content": yaml_content,
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
@app.route("/list_themes")
|
|
55
|
+
def list_themes():
|
|
56
|
+
"""List available theme presets."""
|
|
57
|
+
from ..styles._style_loader import list_presets
|
|
58
|
+
|
|
59
|
+
presets = list_presets()
|
|
60
|
+
current = editor.get_effective_style().get("_name", "SCITEX")
|
|
61
|
+
|
|
62
|
+
return jsonify(
|
|
63
|
+
{
|
|
64
|
+
"themes": presets,
|
|
65
|
+
"current": current,
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
@app.route("/switch_theme", methods=["POST"])
|
|
70
|
+
def switch_theme():
|
|
71
|
+
"""Switch to a different theme preset by reproducing the figure."""
|
|
72
|
+
from .._reproducer import reproduce_from_record
|
|
73
|
+
from ..styles._style_loader import load_preset
|
|
74
|
+
|
|
75
|
+
data = request.get_json() or {}
|
|
76
|
+
theme_name = data.get("theme")
|
|
77
|
+
|
|
78
|
+
if not theme_name:
|
|
79
|
+
return jsonify({"error": "No theme specified"}), 400
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
new_style = load_preset(theme_name)
|
|
83
|
+
|
|
84
|
+
if new_style is None:
|
|
85
|
+
return jsonify({"error": f"Theme '{theme_name}' not found"}), 404
|
|
86
|
+
|
|
87
|
+
# Convert nested style to flat style dict with color_palette
|
|
88
|
+
flat_style = dict(new_style)
|
|
89
|
+
flat_style["_name"] = theme_name
|
|
90
|
+
|
|
91
|
+
# Extract color_palette from nested colors.palette
|
|
92
|
+
if "colors" in new_style and isinstance(new_style["colors"], dict):
|
|
93
|
+
colors_dict = new_style["colors"]
|
|
94
|
+
if "palette" in colors_dict:
|
|
95
|
+
flat_style["color_palette"] = list(colors_dict["palette"])
|
|
96
|
+
|
|
97
|
+
editor.style_overrides.base_style = flat_style
|
|
98
|
+
|
|
99
|
+
if hasattr(editor.fig, "record") and editor.fig.record is not None:
|
|
100
|
+
editor.fig.record.style = flat_style
|
|
101
|
+
new_fig, _ = reproduce_from_record(editor.fig.record)
|
|
102
|
+
editor.fig = new_fig
|
|
103
|
+
# Keep the new style (don't restore old style)
|
|
104
|
+
|
|
105
|
+
mpl_fig = editor.fig.fig if hasattr(editor.fig, "fig") else editor.fig
|
|
106
|
+
behavior = new_style.get("behavior", {})
|
|
107
|
+
for ax in mpl_fig.get_axes():
|
|
108
|
+
hide_top = behavior.get("hide_top_spine", True)
|
|
109
|
+
hide_right = behavior.get("hide_right_spine", True)
|
|
110
|
+
ax.spines["top"].set_visible(not hide_top)
|
|
111
|
+
ax.spines["right"].set_visible(not hide_right)
|
|
112
|
+
|
|
113
|
+
if behavior.get("grid", False):
|
|
114
|
+
ax.grid(True, alpha=0.3)
|
|
115
|
+
else:
|
|
116
|
+
ax.grid(False)
|
|
117
|
+
|
|
118
|
+
base64_img, bboxes, img_size = render_with_overrides(
|
|
119
|
+
editor.fig,
|
|
120
|
+
editor.get_effective_style(),
|
|
121
|
+
editor.dark_mode,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
form_values = get_form_values_from_style(editor.get_effective_style())
|
|
125
|
+
|
|
126
|
+
return jsonify(
|
|
127
|
+
{
|
|
128
|
+
"success": True,
|
|
129
|
+
"theme": theme_name,
|
|
130
|
+
"image": base64_img,
|
|
131
|
+
"bboxes": bboxes,
|
|
132
|
+
"img_size": {"width": img_size[0], "height": img_size[1]},
|
|
133
|
+
"values": form_values,
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
except Exception as e:
|
|
138
|
+
import traceback
|
|
139
|
+
|
|
140
|
+
traceback.print_exc()
|
|
141
|
+
return jsonify({"error": f"Failed to switch theme: {str(e)}"}), 500
|
|
142
|
+
|
|
143
|
+
@app.route("/save", methods=["POST"])
|
|
144
|
+
def save():
|
|
145
|
+
"""Save style overrides (stored separately from recipe)."""
|
|
146
|
+
data = request.get_json() or {}
|
|
147
|
+
editor.style_overrides.update_manual_overrides(data.get("overrides", {}))
|
|
148
|
+
|
|
149
|
+
if editor.recipe_path:
|
|
150
|
+
path = save_overrides(editor.style_overrides, editor.recipe_path)
|
|
151
|
+
return jsonify(
|
|
152
|
+
{
|
|
153
|
+
"success": True,
|
|
154
|
+
"path": str(path),
|
|
155
|
+
"has_overrides": editor.style_overrides.has_manual_overrides(),
|
|
156
|
+
"timestamp": editor.style_overrides.manual_timestamp,
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return jsonify(
|
|
161
|
+
{
|
|
162
|
+
"success": True,
|
|
163
|
+
"overrides": editor.overrides,
|
|
164
|
+
"has_overrides": editor.style_overrides.has_manual_overrides(),
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
@app.route("/restore", methods=["POST"])
|
|
169
|
+
def restore():
|
|
170
|
+
"""Restore to original style (clear manual overrides and axes positions)."""
|
|
171
|
+
from ._bbox import extract_bboxes
|
|
172
|
+
|
|
173
|
+
# Clear all manual overrides (including position overrides)
|
|
174
|
+
editor.style_overrides.clear_manual_overrides()
|
|
175
|
+
|
|
176
|
+
# Restore original axes positions
|
|
177
|
+
editor.restore_axes_positions()
|
|
178
|
+
|
|
179
|
+
if editor._initial_base64 and not editor.dark_mode:
|
|
180
|
+
base64_img = editor._initial_base64
|
|
181
|
+
import base64 as b64
|
|
182
|
+
import io
|
|
183
|
+
|
|
184
|
+
from PIL import Image
|
|
185
|
+
|
|
186
|
+
img_data = b64.b64decode(base64_img)
|
|
187
|
+
img = Image.open(io.BytesIO(img_data))
|
|
188
|
+
img_size = img.size
|
|
189
|
+
mpl_fig = editor.fig.fig if hasattr(editor.fig, "fig") else editor.fig
|
|
190
|
+
original_dpi = mpl_fig.dpi
|
|
191
|
+
mpl_fig.set_dpi(150)
|
|
192
|
+
mpl_fig.canvas.draw()
|
|
193
|
+
bboxes = extract_bboxes(mpl_fig, img_size[0], img_size[1])
|
|
194
|
+
mpl_fig.set_dpi(original_dpi)
|
|
195
|
+
else:
|
|
196
|
+
base64_img, bboxes, img_size = render_with_overrides(
|
|
197
|
+
editor.fig,
|
|
198
|
+
None,
|
|
199
|
+
editor.dark_mode,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return jsonify(
|
|
203
|
+
{
|
|
204
|
+
"success": True,
|
|
205
|
+
"image": base64_img,
|
|
206
|
+
"bboxes": bboxes,
|
|
207
|
+
"img_size": {"width": img_size[0], "height": img_size[1]},
|
|
208
|
+
"original_style": editor.style,
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
@app.route("/diff")
|
|
213
|
+
def get_diff():
|
|
214
|
+
"""Get differences between original and manual overrides."""
|
|
215
|
+
return jsonify(
|
|
216
|
+
{
|
|
217
|
+
"diff": editor.style_overrides.get_diff(),
|
|
218
|
+
"has_overrides": editor.style_overrides.has_manual_overrides(),
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
__all__ = ["register_style_routes"]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
HTML/CSS/JS template builder for figure editor.
|
|
5
|
+
|
|
6
|
+
Uses YAML-compatible flattened key names from to_subplots_kwargs() as the
|
|
7
|
+
single source of truth. No custom key mapping is needed since all keys
|
|
8
|
+
now match the HTML input IDs directly.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import base64
|
|
12
|
+
import json
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Dict, Tuple
|
|
16
|
+
|
|
17
|
+
from ._html import HTML_TEMPLATE
|
|
18
|
+
from ._scripts import SCRIPTS
|
|
19
|
+
from ._styles import STYLES
|
|
20
|
+
|
|
21
|
+
# Server start time for debugging template reloads
|
|
22
|
+
_SERVER_START_TIME = datetime.now().strftime("%H:%M:%S")
|
|
23
|
+
|
|
24
|
+
# Load SciTeX icon as base64
|
|
25
|
+
_SCITEX_ICON_PATH = (
|
|
26
|
+
Path(__file__).parent.parent.parent.parent.parent / "docs" / "scitex-icon.png"
|
|
27
|
+
)
|
|
28
|
+
_SCITEX_ICON_BASE64 = ""
|
|
29
|
+
if _SCITEX_ICON_PATH.exists():
|
|
30
|
+
with open(_SCITEX_ICON_PATH, "rb") as f:
|
|
31
|
+
_SCITEX_ICON_BASE64 = base64.b64encode(f.read()).decode("utf-8")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def build_html_template(
|
|
35
|
+
image_base64: str,
|
|
36
|
+
bboxes: Dict[str, Any],
|
|
37
|
+
color_map: Dict[str, Any],
|
|
38
|
+
style: Dict[str, Any],
|
|
39
|
+
overrides: Dict[str, Any],
|
|
40
|
+
img_size: Tuple[int, int],
|
|
41
|
+
style_name: str = "SCITEX",
|
|
42
|
+
hot_reload: bool = False,
|
|
43
|
+
dark_mode: bool = False,
|
|
44
|
+
) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Build complete HTML template for figure editor.
|
|
47
|
+
|
|
48
|
+
Style keys are expected to be YAML-compatible flattened names that
|
|
49
|
+
match the HTML input IDs directly (e.g., 'fonts_axis_label_pt').
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
image_base64 : str
|
|
54
|
+
Base64-encoded preview image.
|
|
55
|
+
bboxes : dict
|
|
56
|
+
Element bounding boxes.
|
|
57
|
+
color_map : dict
|
|
58
|
+
Hitmap color-to-element mapping.
|
|
59
|
+
style : dict
|
|
60
|
+
Base style configuration with YAML-compatible keys.
|
|
61
|
+
overrides : dict
|
|
62
|
+
Current style overrides with YAML-compatible keys.
|
|
63
|
+
img_size : tuple
|
|
64
|
+
(width, height) of preview image.
|
|
65
|
+
style_name : str
|
|
66
|
+
Name of the applied style preset (e.g., "SCITEX", "MATPLOTLIB").
|
|
67
|
+
hot_reload : bool
|
|
68
|
+
Enable hot reload auto-reconnect JavaScript.
|
|
69
|
+
dark_mode : bool
|
|
70
|
+
Initial dark mode state from saved preferences.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
str
|
|
75
|
+
Complete HTML document.
|
|
76
|
+
"""
|
|
77
|
+
# Merge style and overrides for initial values
|
|
78
|
+
# Keys should already match HTML input IDs (YAML-compatible flattened)
|
|
79
|
+
initial_values = {**style, **overrides}
|
|
80
|
+
|
|
81
|
+
# Hot reload JavaScript for auto-reconnect on server restart
|
|
82
|
+
hot_reload_script = ""
|
|
83
|
+
if hot_reload:
|
|
84
|
+
hot_reload_script = """
|
|
85
|
+
// Hot Reload: Auto-reconnect when server restarts
|
|
86
|
+
(function() {
|
|
87
|
+
let isReconnecting = false;
|
|
88
|
+
let pingInterval = null;
|
|
89
|
+
|
|
90
|
+
function showReloadBanner(show) {
|
|
91
|
+
let banner = document.getElementById('hot-reload-banner');
|
|
92
|
+
if (!banner && show) {
|
|
93
|
+
banner = document.createElement('div');
|
|
94
|
+
banner.id = 'hot-reload-banner';
|
|
95
|
+
banner.style.cssText = 'position:fixed;top:0;left:0;right:0;background:#f59e0b;' +
|
|
96
|
+
'color:#000;text-align:center;padding:8px;z-index:9999;font-weight:bold;';
|
|
97
|
+
banner.textContent = 'Server restarting... will reload automatically';
|
|
98
|
+
document.body.prepend(banner);
|
|
99
|
+
}
|
|
100
|
+
if (banner) {
|
|
101
|
+
banner.style.display = show ? 'block' : 'none';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function ping() {
|
|
106
|
+
fetch('/ping', {cache: 'no-store'})
|
|
107
|
+
.then(r => {
|
|
108
|
+
if (r.ok && isReconnecting) {
|
|
109
|
+
// Server is back! Reload the page
|
|
110
|
+
console.log('[Hot Reload] Server is back, reloading...');
|
|
111
|
+
window.location.reload();
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
.catch(() => {
|
|
115
|
+
if (!isReconnecting) {
|
|
116
|
+
console.log('[Hot Reload] Server disconnected, waiting for restart...');
|
|
117
|
+
isReconnecting = true;
|
|
118
|
+
showReloadBanner(true);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Start pinging every 500ms
|
|
124
|
+
pingInterval = setInterval(ping, 500);
|
|
125
|
+
console.log('[Hot Reload] Enabled - watching for server restarts');
|
|
126
|
+
})();
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
# Inject data into template
|
|
130
|
+
html = HTML_TEMPLATE
|
|
131
|
+
html = html.replace("/* STYLES_PLACEHOLDER */", STYLES)
|
|
132
|
+
html = html.replace("/* SCRIPTS_PLACEHOLDER */", SCRIPTS + hot_reload_script)
|
|
133
|
+
html = html.replace("IMAGE_BASE64_PLACEHOLDER", image_base64)
|
|
134
|
+
html = html.replace("BBOXES_PLACEHOLDER", json.dumps(bboxes))
|
|
135
|
+
html = html.replace("COLOR_MAP_PLACEHOLDER", json.dumps(color_map))
|
|
136
|
+
html = html.replace("INITIAL_VALUES_PLACEHOLDER", json.dumps(initial_values))
|
|
137
|
+
html = html.replace("IMG_WIDTH_PLACEHOLDER", str(img_size[0]))
|
|
138
|
+
html = html.replace("IMG_HEIGHT_PLACEHOLDER", str(img_size[1]))
|
|
139
|
+
html = html.replace("STYLE_NAME_PLACEHOLDER", style_name)
|
|
140
|
+
html = html.replace("SCITEX_ICON_PLACEHOLDER", _SCITEX_ICON_BASE64)
|
|
141
|
+
|
|
142
|
+
# Dark mode preference - set initial state
|
|
143
|
+
html = html.replace("DARK_MODE_THEME_PLACEHOLDER", "dark" if dark_mode else "light")
|
|
144
|
+
html = html.replace("DARK_MODE_CHECKED_PLACEHOLDER", "checked" if dark_mode else "")
|
|
145
|
+
|
|
146
|
+
# Server start time for debugging
|
|
147
|
+
html = html.replace("SERVER_START_TIME_PLACEHOLDER", _SERVER_START_TIME)
|
|
148
|
+
|
|
149
|
+
return html
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
__all__ = ["build_html_template"]
|