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,464 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Label and axis controls JavaScript for the figure editor.
|
|
4
|
+
|
|
5
|
+
This module contains the JavaScript code for:
|
|
6
|
+
- Label input handlers (title, xlabel, ylabel, suptitle)
|
|
7
|
+
- Axis type toggles (numerical/categorical)
|
|
8
|
+
- Legend position controls
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
SCRIPTS_LABELS = """
|
|
12
|
+
// ===== LABEL AND AXIS CONTROLS =====
|
|
13
|
+
|
|
14
|
+
// Load current axis labels from server
|
|
15
|
+
async function loadLabels() {
|
|
16
|
+
try {
|
|
17
|
+
const response = await fetch('/get_labels');
|
|
18
|
+
const labels = await response.json();
|
|
19
|
+
|
|
20
|
+
const titleInput = document.getElementById('label_title');
|
|
21
|
+
const xlabelInput = document.getElementById('label_xlabel');
|
|
22
|
+
const ylabelInput = document.getElementById('label_ylabel');
|
|
23
|
+
const suptitleInput = document.getElementById('label_suptitle');
|
|
24
|
+
|
|
25
|
+
if (titleInput) titleInput.value = labels.title || '';
|
|
26
|
+
if (xlabelInput) xlabelInput.value = labels.xlabel || '';
|
|
27
|
+
if (ylabelInput) ylabelInput.value = labels.ylabel || '';
|
|
28
|
+
if (suptitleInput) suptitleInput.value = labels.suptitle || '';
|
|
29
|
+
|
|
30
|
+
console.log('Loaded labels:', labels);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Failed to load labels:', error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Update axis label on server
|
|
37
|
+
async function updateLabel(labelType, text) {
|
|
38
|
+
console.log(`Updating ${labelType} to: "${text}"`);
|
|
39
|
+
document.body.classList.add('loading');
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch('/update_label', {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: { 'Content-Type': 'application/json' },
|
|
45
|
+
body: JSON.stringify({ label_type: labelType, text: text })
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const data = await response.json();
|
|
49
|
+
|
|
50
|
+
if (data.success) {
|
|
51
|
+
const img = document.getElementById('preview-image');
|
|
52
|
+
img.src = 'data:image/png;base64,' + data.image;
|
|
53
|
+
|
|
54
|
+
if (data.img_size) {
|
|
55
|
+
currentImgWidth = data.img_size.width;
|
|
56
|
+
currentImgHeight = data.img_size.height;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
currentBboxes = data.bboxes;
|
|
60
|
+
updateHitRegions();
|
|
61
|
+
console.log('Label updated successfully');
|
|
62
|
+
} else {
|
|
63
|
+
console.error('Label update failed:', data.error);
|
|
64
|
+
alert('Update failed: ' + data.error);
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error('Label update failed:', error);
|
|
68
|
+
alert('Update failed: ' + error.message);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
document.body.classList.remove('loading');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Initialize label input event handlers
|
|
75
|
+
function initializeLabelInputs() {
|
|
76
|
+
const labelMap = {
|
|
77
|
+
'label_title': 'title',
|
|
78
|
+
'label_xlabel': 'xlabel',
|
|
79
|
+
'label_ylabel': 'ylabel',
|
|
80
|
+
'label_suptitle': 'suptitle'
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
for (const [inputId, labelType] of Object.entries(labelMap)) {
|
|
84
|
+
const input = document.getElementById(inputId);
|
|
85
|
+
if (input) {
|
|
86
|
+
let timeout;
|
|
87
|
+
input.addEventListener('input', function() {
|
|
88
|
+
clearTimeout(timeout);
|
|
89
|
+
timeout = setTimeout(() => {
|
|
90
|
+
updateLabel(labelType, this.value);
|
|
91
|
+
}, UPDATE_DEBOUNCE);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
input.addEventListener('keydown', function(e) {
|
|
95
|
+
if (e.key === 'Enter') {
|
|
96
|
+
clearTimeout(timeout);
|
|
97
|
+
updateLabel(labelType, this.value);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
input.addEventListener('blur', function() {
|
|
102
|
+
clearTimeout(timeout);
|
|
103
|
+
updateLabel(labelType, this.value);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
initializeAxisTypeToggles();
|
|
109
|
+
initializeLegendPosition();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Initialize axis type toggle buttons
|
|
113
|
+
function initializeAxisTypeToggles() {
|
|
114
|
+
const xNumerical = document.getElementById('xaxis-numerical');
|
|
115
|
+
const xCategorical = document.getElementById('xaxis-categorical');
|
|
116
|
+
const yNumerical = document.getElementById('yaxis-numerical');
|
|
117
|
+
const yCategorical = document.getElementById('yaxis-categorical');
|
|
118
|
+
const xLabelsRow = document.getElementById('xaxis-labels-row');
|
|
119
|
+
const yLabelsRow = document.getElementById('yaxis-labels-row');
|
|
120
|
+
const xLabelsInput = document.getElementById('xaxis_labels');
|
|
121
|
+
const yLabelsInput = document.getElementById('yaxis_labels');
|
|
122
|
+
|
|
123
|
+
if (xNumerical) {
|
|
124
|
+
xNumerical.addEventListener('click', () => {
|
|
125
|
+
xNumerical.classList.add('active');
|
|
126
|
+
xCategorical.classList.remove('active');
|
|
127
|
+
xLabelsRow.style.display = 'none';
|
|
128
|
+
updateAxisType('x', 'numerical');
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (xCategorical) {
|
|
133
|
+
xCategorical.addEventListener('click', () => {
|
|
134
|
+
xCategorical.classList.add('active');
|
|
135
|
+
xNumerical.classList.remove('active');
|
|
136
|
+
xLabelsRow.style.display = 'flex';
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (yNumerical) {
|
|
141
|
+
yNumerical.addEventListener('click', () => {
|
|
142
|
+
yNumerical.classList.add('active');
|
|
143
|
+
yCategorical.classList.remove('active');
|
|
144
|
+
yLabelsRow.style.display = 'none';
|
|
145
|
+
updateAxisType('y', 'numerical');
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (yCategorical) {
|
|
150
|
+
yCategorical.addEventListener('click', () => {
|
|
151
|
+
yCategorical.classList.add('active');
|
|
152
|
+
yNumerical.classList.remove('active');
|
|
153
|
+
yLabelsRow.style.display = 'flex';
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Labels input handlers
|
|
158
|
+
[xLabelsInput, yLabelsInput].forEach((input, idx) => {
|
|
159
|
+
const axis = idx === 0 ? 'x' : 'y';
|
|
160
|
+
if (input) {
|
|
161
|
+
let timeout;
|
|
162
|
+
input.addEventListener('input', function() {
|
|
163
|
+
clearTimeout(timeout);
|
|
164
|
+
timeout = setTimeout(() => {
|
|
165
|
+
const labels = this.value.split(',').map(l => l.trim()).filter(l => l);
|
|
166
|
+
if (labels.length > 0) updateAxisType(axis, 'categorical', labels);
|
|
167
|
+
}, UPDATE_DEBOUNCE);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
input.addEventListener('keydown', function(e) {
|
|
171
|
+
if (e.key === 'Enter') {
|
|
172
|
+
clearTimeout(timeout);
|
|
173
|
+
const labels = this.value.split(',').map(l => l.trim()).filter(l => l);
|
|
174
|
+
if (labels.length > 0) updateAxisType(axis, 'categorical', labels);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
loadAxisInfo();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Load current axis type info
|
|
184
|
+
async function loadAxisInfo() {
|
|
185
|
+
try {
|
|
186
|
+
const response = await fetch('/get_axis_info');
|
|
187
|
+
const info = await response.json();
|
|
188
|
+
|
|
189
|
+
if (info.x_type === 'categorical') {
|
|
190
|
+
document.getElementById('xaxis-categorical')?.classList.add('active');
|
|
191
|
+
document.getElementById('xaxis-numerical')?.classList.remove('active');
|
|
192
|
+
const xLabelsRow = document.getElementById('xaxis-labels-row');
|
|
193
|
+
if (xLabelsRow) xLabelsRow.style.display = 'flex';
|
|
194
|
+
if (info.x_labels?.length > 0) {
|
|
195
|
+
const input = document.getElementById('xaxis_labels');
|
|
196
|
+
if (input) input.value = info.x_labels.join(', ');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (info.y_type === 'categorical') {
|
|
201
|
+
document.getElementById('yaxis-categorical')?.classList.add('active');
|
|
202
|
+
document.getElementById('yaxis-numerical')?.classList.remove('active');
|
|
203
|
+
const yLabelsRow = document.getElementById('yaxis-labels-row');
|
|
204
|
+
if (yLabelsRow) yLabelsRow.style.display = 'flex';
|
|
205
|
+
if (info.y_labels?.length > 0) {
|
|
206
|
+
const input = document.getElementById('yaxis_labels');
|
|
207
|
+
if (input) input.value = info.y_labels.join(', ');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log('Loaded axis info:', info);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error('Failed to load axis info:', error);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Update axis type on server
|
|
218
|
+
async function updateAxisType(axis, type, labels = []) {
|
|
219
|
+
console.log(`Updating ${axis} axis to ${type}`, labels);
|
|
220
|
+
document.body.classList.add('loading');
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const response = await fetch('/update_axis_type', {
|
|
224
|
+
method: 'POST',
|
|
225
|
+
headers: { 'Content-Type': 'application/json' },
|
|
226
|
+
body: JSON.stringify({ axis, type, labels })
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const data = await response.json();
|
|
230
|
+
|
|
231
|
+
if (data.success) {
|
|
232
|
+
const img = document.getElementById('preview-image');
|
|
233
|
+
img.src = 'data:image/png;base64,' + data.image;
|
|
234
|
+
|
|
235
|
+
if (data.img_size) {
|
|
236
|
+
currentImgWidth = data.img_size.width;
|
|
237
|
+
currentImgHeight = data.img_size.height;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
currentBboxes = data.bboxes;
|
|
241
|
+
updateHitRegions();
|
|
242
|
+
console.log('Axis type updated successfully');
|
|
243
|
+
} else {
|
|
244
|
+
console.error('Axis type update failed:', data.error);
|
|
245
|
+
alert('Update failed: ' + data.error);
|
|
246
|
+
}
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.error('Axis type update failed:', error);
|
|
249
|
+
alert('Update failed: ' + error.message);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
document.body.classList.remove('loading');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Initialize legend position controls
|
|
256
|
+
function initializeLegendPosition() {
|
|
257
|
+
const locSelect = document.getElementById('legend_loc');
|
|
258
|
+
const customPosDiv = document.getElementById('legend-custom-pos');
|
|
259
|
+
const xInput = document.getElementById('legend_x');
|
|
260
|
+
const yInput = document.getElementById('legend_y');
|
|
261
|
+
const visibleCheckbox = document.getElementById('legend_visible');
|
|
262
|
+
|
|
263
|
+
if (!locSelect) return;
|
|
264
|
+
|
|
265
|
+
if (visibleCheckbox) {
|
|
266
|
+
visibleCheckbox.addEventListener('change', function() {
|
|
267
|
+
updateLegendVisibility(this.checked);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
locSelect.addEventListener('change', function() {
|
|
272
|
+
if (this.value === 'custom') {
|
|
273
|
+
customPosDiv.style.display = 'block';
|
|
274
|
+
} else {
|
|
275
|
+
customPosDiv.style.display = 'none';
|
|
276
|
+
updateLegendPosition(this.value);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
if (xInput && yInput) {
|
|
281
|
+
let timeout;
|
|
282
|
+
const updateCustomPos = () => {
|
|
283
|
+
clearTimeout(timeout);
|
|
284
|
+
timeout = setTimeout(() => {
|
|
285
|
+
const x = parseFloat(xInput.value);
|
|
286
|
+
const y = parseFloat(yInput.value);
|
|
287
|
+
if (!isNaN(x) && !isNaN(y)) updateLegendPosition('custom', x, y);
|
|
288
|
+
}, UPDATE_DEBOUNCE);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
xInput.addEventListener('input', updateCustomPos);
|
|
292
|
+
yInput.addEventListener('input', updateCustomPos);
|
|
293
|
+
|
|
294
|
+
[xInput, yInput].forEach(input => {
|
|
295
|
+
input.addEventListener('keydown', (e) => {
|
|
296
|
+
if (e.key === 'Enter') {
|
|
297
|
+
clearTimeout(timeout);
|
|
298
|
+
const x = parseFloat(xInput.value);
|
|
299
|
+
const y = parseFloat(yInput.value);
|
|
300
|
+
if (!isNaN(x) && !isNaN(y)) updateLegendPosition('custom', x, y);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
loadLegendInfo();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Load current legend position info
|
|
310
|
+
async function loadLegendInfo() {
|
|
311
|
+
try {
|
|
312
|
+
const response = await fetch('/get_legend_info');
|
|
313
|
+
const info = await response.json();
|
|
314
|
+
|
|
315
|
+
if (!info.has_legend) {
|
|
316
|
+
console.log('No legend found');
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const locSelect = document.getElementById('legend_loc');
|
|
321
|
+
const customPosDiv = document.getElementById('legend-custom-pos');
|
|
322
|
+
const xInput = document.getElementById('legend_x');
|
|
323
|
+
const yInput = document.getElementById('legend_y');
|
|
324
|
+
const visibleCheckbox = document.getElementById('legend_visible');
|
|
325
|
+
|
|
326
|
+
if (visibleCheckbox) visibleCheckbox.checked = info.visible !== false;
|
|
327
|
+
if (locSelect) locSelect.value = info.loc;
|
|
328
|
+
|
|
329
|
+
if (info.loc === 'custom' && customPosDiv) {
|
|
330
|
+
customPosDiv.style.display = 'block';
|
|
331
|
+
if (xInput && info.x !== null) xInput.value = info.x;
|
|
332
|
+
if (yInput && info.y !== null) yInput.value = info.y;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
console.log('Loaded legend info:', info);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
console.error('Failed to load legend info:', error);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Update legend visibility
|
|
342
|
+
async function updateLegendVisibility(visible) {
|
|
343
|
+
console.log('Updating legend visibility:', visible);
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
const response = await fetch('/update_legend_position', {
|
|
347
|
+
method: 'POST',
|
|
348
|
+
headers: { 'Content-Type': 'application/json' },
|
|
349
|
+
body: JSON.stringify({ visible })
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const result = await response.json();
|
|
353
|
+
|
|
354
|
+
if (result.success) {
|
|
355
|
+
const previewImg = document.getElementById('preview-image');
|
|
356
|
+
previewImg.src = 'data:image/png;base64,' + result.image;
|
|
357
|
+
|
|
358
|
+
if (result.img_size) {
|
|
359
|
+
currentImgWidth = result.img_size.width;
|
|
360
|
+
currentImgHeight = result.img_size.height;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (result.bboxes) {
|
|
364
|
+
currentBboxes = result.bboxes;
|
|
365
|
+
previewImg.onload = () => {
|
|
366
|
+
updateHitRegions();
|
|
367
|
+
loadHitmap();
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
console.error('Legend visibility update failed:', result.error);
|
|
372
|
+
}
|
|
373
|
+
} catch (error) {
|
|
374
|
+
console.error('Failed to update legend visibility:', error);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Update legend position on server
|
|
379
|
+
async function updateLegendPosition(loc, x = null, y = null) {
|
|
380
|
+
console.log(`Updating legend position: loc=${loc}, x=${x}, y=${y}`);
|
|
381
|
+
document.body.classList.add('loading');
|
|
382
|
+
|
|
383
|
+
try {
|
|
384
|
+
const body = { loc };
|
|
385
|
+
if (loc === 'custom' && x !== null && y !== null) {
|
|
386
|
+
body.x = x;
|
|
387
|
+
body.y = y;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const response = await fetch('/update_legend_position', {
|
|
391
|
+
method: 'POST',
|
|
392
|
+
headers: { 'Content-Type': 'application/json' },
|
|
393
|
+
body: JSON.stringify(body)
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
const data = await response.json();
|
|
397
|
+
|
|
398
|
+
if (data.success) {
|
|
399
|
+
const img = document.getElementById('preview-image');
|
|
400
|
+
img.src = 'data:image/png;base64,' + data.image;
|
|
401
|
+
|
|
402
|
+
if (data.img_size) {
|
|
403
|
+
currentImgWidth = data.img_size.width;
|
|
404
|
+
currentImgHeight = data.img_size.height;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
currentBboxes = data.bboxes;
|
|
408
|
+
updateHitRegions();
|
|
409
|
+
console.log('Legend position updated successfully');
|
|
410
|
+
} else {
|
|
411
|
+
console.error('Legend position update failed:', data.error);
|
|
412
|
+
if (!data.error.includes('No legend')) {
|
|
413
|
+
alert('Update failed: ' + data.error);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
} catch (error) {
|
|
417
|
+
console.error('Legend position update failed:', error);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
document.body.classList.remove('loading');
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Initialize download dropdown
|
|
424
|
+
function initializeDownloadDropdown() {
|
|
425
|
+
const mainBtn = document.getElementById('btn-download-main');
|
|
426
|
+
const toggleBtn = document.getElementById('btn-download-toggle');
|
|
427
|
+
const menu = document.getElementById('download-menu');
|
|
428
|
+
|
|
429
|
+
// Download dropdown state
|
|
430
|
+
let currentDownloadFormat = 'png';
|
|
431
|
+
|
|
432
|
+
mainBtn?.addEventListener('click', () => downloadFigure(currentDownloadFormat));
|
|
433
|
+
|
|
434
|
+
toggleBtn?.addEventListener('click', (e) => {
|
|
435
|
+
e.stopPropagation();
|
|
436
|
+
menu.classList.toggle('open');
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
document.querySelectorAll('.download-option').forEach(option => {
|
|
440
|
+
option.addEventListener('click', () => {
|
|
441
|
+
const format = option.dataset.format;
|
|
442
|
+
currentDownloadFormat = format;
|
|
443
|
+
mainBtn.textContent = 'Download ' + format.toUpperCase();
|
|
444
|
+
|
|
445
|
+
document.querySelectorAll('.download-option').forEach(opt => {
|
|
446
|
+
opt.classList.toggle('active', opt.dataset.format === format);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
menu.classList.remove('open');
|
|
450
|
+
downloadFigure(format);
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
document.addEventListener('click', (e) => {
|
|
455
|
+
if (!e.target.closest('.download-dropdown')) {
|
|
456
|
+
menu?.classList.remove('open');
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
"""
|
|
461
|
+
|
|
462
|
+
__all__ = ["SCRIPTS_LABELS"]
|
|
463
|
+
|
|
464
|
+
# EOF
|