figrecipe 0.6.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 +106 -973
- 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 +2 -93
- figrecipe/_dev/_plotters.py +76 -0
- figrecipe/_dev/_run_demos.py +56 -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 +57 -9
- 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 +68 -1039
- 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/_preferences.py +135 -0
- figrecipe/_editor/_render_overrides.py +480 -0
- figrecipe/_editor/_renderer.py +35 -185
- 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 +78 -1
- figrecipe/_editor/_templates/_html.py +109 -13
- 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 +6 -0
- figrecipe/_recorder.py +35 -106
- 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/_signatures/_kwargs.py +273 -0
- figrecipe/_signatures/_loader.py +21 -423
- figrecipe/_signatures/_parsing.py +147 -0
- figrecipe/_wrappers/_axes.py +119 -910
- 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 +162 -0
- figrecipe/_wrappers/_panel_labels.py +127 -0
- figrecipe/_wrappers/_plot_helpers.py +143 -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 +32 -478
- 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 +29 -24
- {figrecipe-0.6.0.dist-info → figrecipe-0.7.4.dist-info}/METADATA +37 -2
- figrecipe-0.7.4.dist-info/RECORD +188 -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/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.7.4.dist-info}/WHEEL +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.7.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Element editor JavaScript for the figure editor.
|
|
4
|
+
|
|
5
|
+
This module contains the JavaScript code for:
|
|
6
|
+
- Dynamic form field creation
|
|
7
|
+
- Call properties display
|
|
8
|
+
- Parameter change handling
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
SCRIPTS_ELEMENT_EDITOR = """
|
|
12
|
+
// ===== ELEMENT EDITOR =====
|
|
13
|
+
|
|
14
|
+
// Create a dynamic form field for call parameter
|
|
15
|
+
function createDynamicField(callId, key, value, sigInfo, isUnused = false) {
|
|
16
|
+
const container = document.createElement('div');
|
|
17
|
+
container.className = 'dynamic-field-container' + (isUnused ? ' unused' : '');
|
|
18
|
+
|
|
19
|
+
const row = document.createElement('div');
|
|
20
|
+
row.className = 'form-row dynamic-field';
|
|
21
|
+
|
|
22
|
+
const label = document.createElement('label');
|
|
23
|
+
label.textContent = key;
|
|
24
|
+
|
|
25
|
+
let input;
|
|
26
|
+
|
|
27
|
+
// Handle color list fields (e.g., pie chart colors array)
|
|
28
|
+
if (isColorListField(key, value)) {
|
|
29
|
+
input = createColorListInput(callId, key, value);
|
|
30
|
+
row.appendChild(label);
|
|
31
|
+
row.appendChild(input);
|
|
32
|
+
container.appendChild(row);
|
|
33
|
+
return container;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (isColorField(key, sigInfo)) {
|
|
37
|
+
input = createColorInput(callId, key, value);
|
|
38
|
+
row.appendChild(label);
|
|
39
|
+
row.appendChild(input);
|
|
40
|
+
container.appendChild(row);
|
|
41
|
+
return container;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof value === 'boolean' || value === true || value === false) {
|
|
45
|
+
input = document.createElement('input');
|
|
46
|
+
input.type = 'checkbox';
|
|
47
|
+
input.checked = value === true;
|
|
48
|
+
} else if (typeof value === 'number') {
|
|
49
|
+
input = document.createElement('input');
|
|
50
|
+
input.type = 'number';
|
|
51
|
+
input.step = 'any';
|
|
52
|
+
input.value = value;
|
|
53
|
+
} else if (value === null || value === undefined) {
|
|
54
|
+
input = document.createElement('input');
|
|
55
|
+
input.type = 'text';
|
|
56
|
+
input.value = '';
|
|
57
|
+
input.placeholder = 'null';
|
|
58
|
+
} else {
|
|
59
|
+
input = document.createElement('input');
|
|
60
|
+
input.type = 'text';
|
|
61
|
+
input.value = String(value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
input.dataset.callId = callId;
|
|
65
|
+
input.dataset.param = key;
|
|
66
|
+
input.className = 'dynamic-input';
|
|
67
|
+
|
|
68
|
+
input.addEventListener('change', function() {
|
|
69
|
+
handleDynamicParamChange(callId, key, this);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
row.appendChild(label);
|
|
73
|
+
row.appendChild(input);
|
|
74
|
+
container.appendChild(row);
|
|
75
|
+
|
|
76
|
+
if (sigInfo?.type) {
|
|
77
|
+
const typeHint = document.createElement('div');
|
|
78
|
+
typeHint.className = 'type-hint';
|
|
79
|
+
let typeText = sigInfo.type
|
|
80
|
+
.replace(/:mpltype:`([^`]+)`/g, '$1')
|
|
81
|
+
.replace(/`~[^`]+`/g, '')
|
|
82
|
+
.replace(/`([^`]+)`/g, '$1');
|
|
83
|
+
typeHint.textContent = typeText;
|
|
84
|
+
container.appendChild(typeHint);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return container;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Show dynamic call properties for selected element
|
|
91
|
+
function showDynamicCallProperties(element) {
|
|
92
|
+
const container = document.getElementById('dynamic-call-properties');
|
|
93
|
+
if (!container) return;
|
|
94
|
+
|
|
95
|
+
container.innerHTML = '';
|
|
96
|
+
|
|
97
|
+
const callId = element.call_id || element.label;
|
|
98
|
+
|
|
99
|
+
// If no call data found, show basic element info instead
|
|
100
|
+
if (!callId || !callsData[callId]) {
|
|
101
|
+
container.style.display = 'block';
|
|
102
|
+
|
|
103
|
+
// Create header with element type
|
|
104
|
+
const header = document.createElement('div');
|
|
105
|
+
header.className = 'dynamic-props-header';
|
|
106
|
+
const elemType = element.type || 'element';
|
|
107
|
+
const elemLabel = element.label || callId || 'unknown';
|
|
108
|
+
header.innerHTML = `<strong>${elemType}</strong> <span class="call-id">${elemLabel}</span>`;
|
|
109
|
+
container.appendChild(header);
|
|
110
|
+
|
|
111
|
+
// Show basic info section
|
|
112
|
+
const infoSection = document.createElement('div');
|
|
113
|
+
infoSection.className = 'dynamic-props-section';
|
|
114
|
+
infoSection.innerHTML = '<div class="dynamic-props-label">Element Info:</div>';
|
|
115
|
+
|
|
116
|
+
// Show type
|
|
117
|
+
const typeRow = document.createElement('div');
|
|
118
|
+
typeRow.className = 'form-row dynamic-field';
|
|
119
|
+
typeRow.innerHTML = `<label>Type</label><span class="arg-value">${elemType}</span>`;
|
|
120
|
+
infoSection.appendChild(typeRow);
|
|
121
|
+
|
|
122
|
+
// Show color if available
|
|
123
|
+
if (element.original_color) {
|
|
124
|
+
const colorRow = document.createElement('div');
|
|
125
|
+
colorRow.className = 'form-row dynamic-field';
|
|
126
|
+
colorRow.innerHTML = `<label>Color</label><span class="arg-value" style="color:${element.original_color}">${element.original_color}</span>`;
|
|
127
|
+
infoSection.appendChild(colorRow);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Show axes index
|
|
131
|
+
if (element.ax_index !== undefined) {
|
|
132
|
+
const axRow = document.createElement('div');
|
|
133
|
+
axRow.className = 'form-row dynamic-field';
|
|
134
|
+
axRow.innerHTML = `<label>Axes</label><span class="arg-value">ax_${element.ax_index}</span>`;
|
|
135
|
+
infoSection.appendChild(axRow);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
container.appendChild(infoSection);
|
|
139
|
+
|
|
140
|
+
// Add note about no recorded call
|
|
141
|
+
const noteDiv = document.createElement('div');
|
|
142
|
+
noteDiv.className = 'dynamic-props-note';
|
|
143
|
+
noteDiv.style.cssText = 'font-size: 11px; color: var(--text-secondary); margin-top: 8px; font-style: italic;';
|
|
144
|
+
noteDiv.textContent = 'No recorded call data available for this element.';
|
|
145
|
+
container.appendChild(noteDiv);
|
|
146
|
+
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const callData = callsData[callId];
|
|
151
|
+
container.style.display = 'block';
|
|
152
|
+
|
|
153
|
+
// Create header
|
|
154
|
+
const header = document.createElement('div');
|
|
155
|
+
header.className = 'dynamic-props-header';
|
|
156
|
+
header.innerHTML = `<strong>${callData.function}()</strong> <span class="call-id">${callId}</span>`;
|
|
157
|
+
container.appendChild(header);
|
|
158
|
+
|
|
159
|
+
const usedArgs = callData.args || [];
|
|
160
|
+
const usedKwargs = { ...callData.kwargs } || {};
|
|
161
|
+
const sigArgs = callData.signature?.args || [];
|
|
162
|
+
const sigKwargs = callData.signature?.kwargs || {};
|
|
163
|
+
|
|
164
|
+
// Get representative color if not set
|
|
165
|
+
if (!usedKwargs.color && !usedKwargs.c) {
|
|
166
|
+
if ('color' in sigKwargs || 'c' in sigKwargs) {
|
|
167
|
+
const hitmapCallId = element.call_id;
|
|
168
|
+
const groupColor = getGroupRepresentativeColor(hitmapCallId, element.original_color) ||
|
|
169
|
+
element.original_color;
|
|
170
|
+
if (groupColor) {
|
|
171
|
+
usedKwargs.color = groupColor;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Show args (read-only)
|
|
177
|
+
if (usedArgs.length > 0) {
|
|
178
|
+
const argsSection = document.createElement('div');
|
|
179
|
+
argsSection.className = 'dynamic-props-section';
|
|
180
|
+
argsSection.innerHTML = '<div class="dynamic-props-label">Arguments:</div>';
|
|
181
|
+
|
|
182
|
+
for (let i = 0; i < usedArgs.length; i++) {
|
|
183
|
+
const arg = usedArgs[i];
|
|
184
|
+
const sigArg = sigArgs[i] || {};
|
|
185
|
+
const row = document.createElement('div');
|
|
186
|
+
row.className = 'form-row dynamic-field arg-field';
|
|
187
|
+
|
|
188
|
+
const label = document.createElement('label');
|
|
189
|
+
label.textContent = arg.name;
|
|
190
|
+
if (sigArg.type) label.title = `Type: ${sigArg.type}`;
|
|
191
|
+
if (sigArg.optional) label.textContent += ' (opt)';
|
|
192
|
+
|
|
193
|
+
const valueSpan = document.createElement('span');
|
|
194
|
+
valueSpan.className = 'arg-value';
|
|
195
|
+
if (arg.data && Array.isArray(arg.data)) {
|
|
196
|
+
valueSpan.textContent = `[${arg.data.length} items]`;
|
|
197
|
+
} else if (arg.data === '__FILE__') {
|
|
198
|
+
valueSpan.textContent = '[external file]';
|
|
199
|
+
} else {
|
|
200
|
+
valueSpan.textContent = String(arg.data).substring(0, 30);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
row.appendChild(label);
|
|
204
|
+
row.appendChild(valueSpan);
|
|
205
|
+
argsSection.appendChild(row);
|
|
206
|
+
}
|
|
207
|
+
container.appendChild(argsSection);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Show used kwargs (editable)
|
|
211
|
+
if (Object.keys(usedKwargs).length > 0) {
|
|
212
|
+
const usedSection = document.createElement('div');
|
|
213
|
+
usedSection.className = 'dynamic-props-section';
|
|
214
|
+
usedSection.innerHTML = '<div class="dynamic-props-label">Used Parameters:</div>';
|
|
215
|
+
|
|
216
|
+
for (const [key, value] of Object.entries(usedKwargs)) {
|
|
217
|
+
const field = createDynamicField(callId, key, value, sigKwargs[key]);
|
|
218
|
+
usedSection.appendChild(field);
|
|
219
|
+
}
|
|
220
|
+
container.appendChild(usedSection);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Show available (unused) params
|
|
224
|
+
const availableParams = Object.keys(sigKwargs).filter(k => !(k in usedKwargs));
|
|
225
|
+
if (availableParams.length > 0) {
|
|
226
|
+
const availSection = document.createElement('details');
|
|
227
|
+
availSection.className = 'dynamic-props-available';
|
|
228
|
+
availSection.setAttribute('open', '');
|
|
229
|
+
availSection.innerHTML = `<summary>Available Parameters (${availableParams.length})</summary>`;
|
|
230
|
+
|
|
231
|
+
const availContent = document.createElement('div');
|
|
232
|
+
availContent.className = 'dynamic-props-section';
|
|
233
|
+
for (const key of availableParams) {
|
|
234
|
+
const sigInfo = sigKwargs[key];
|
|
235
|
+
const field = createDynamicField(callId, key, sigInfo?.default, sigInfo, true);
|
|
236
|
+
availContent.appendChild(field);
|
|
237
|
+
}
|
|
238
|
+
availSection.appendChild(availContent);
|
|
239
|
+
container.appendChild(availSection);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Handle change to dynamic call parameter
|
|
244
|
+
async function handleDynamicParamChange(callId, param, input) {
|
|
245
|
+
let value;
|
|
246
|
+
if (input.type === 'checkbox') {
|
|
247
|
+
value = input.checked;
|
|
248
|
+
} else if (input.type === 'number') {
|
|
249
|
+
value = parseFloat(input.value);
|
|
250
|
+
if (isNaN(value)) value = null;
|
|
251
|
+
} else {
|
|
252
|
+
value = input.value || null;
|
|
253
|
+
if (value === 'null') value = null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Resolve color to hex
|
|
257
|
+
const colorParams = ['color', 'facecolor', 'edgecolor', 'markerfacecolor', 'markeredgecolor', 'c'];
|
|
258
|
+
if (value && typeof value === 'string' && colorParams.includes(param.toLowerCase())) {
|
|
259
|
+
value = resolveColorToHex(value);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
console.log(`Dynamic param change: ${callId}.${param} = ${value}`);
|
|
263
|
+
|
|
264
|
+
document.body.classList.add('loading');
|
|
265
|
+
if (input.disabled !== undefined) input.disabled = true;
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const response = await fetch('/update_call', {
|
|
269
|
+
method: 'POST',
|
|
270
|
+
headers: { 'Content-Type': 'application/json' },
|
|
271
|
+
body: JSON.stringify({ call_id: callId, param: param, value: value })
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const data = await response.json();
|
|
275
|
+
|
|
276
|
+
if (data.success) {
|
|
277
|
+
const img = document.getElementById('preview-image');
|
|
278
|
+
img.src = 'data:image/png;base64,' + data.image;
|
|
279
|
+
|
|
280
|
+
if (data.img_size) {
|
|
281
|
+
currentImgWidth = data.img_size.width;
|
|
282
|
+
currentImgHeight = data.img_size.height;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
currentBboxes = data.bboxes;
|
|
286
|
+
loadHitmap();
|
|
287
|
+
updateHitRegions();
|
|
288
|
+
|
|
289
|
+
if (callsData[callId]) {
|
|
290
|
+
if (value === null) {
|
|
291
|
+
delete callsData[callId].kwargs[param];
|
|
292
|
+
} else {
|
|
293
|
+
callsData[callId].kwargs[param] = value;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
alert('Update failed: ' + data.error);
|
|
298
|
+
}
|
|
299
|
+
} catch (error) {
|
|
300
|
+
alert('Update failed: ' + error.message);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (input.disabled !== undefined) input.disabled = false;
|
|
304
|
+
document.body.classList.remove('loading');
|
|
305
|
+
}
|
|
306
|
+
"""
|
|
307
|
+
|
|
308
|
+
__all__ = ["SCRIPTS_ELEMENT_EDITOR"]
|
|
309
|
+
|
|
310
|
+
# EOF
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""File switcher JavaScript for switching between recipe files."""
|
|
4
|
+
|
|
5
|
+
SCRIPTS_FILES = """
|
|
6
|
+
// ==================== FILE SWITCHER ====================
|
|
7
|
+
// Allows switching between recipe files without restarting the server
|
|
8
|
+
|
|
9
|
+
let currentFilePath = null;
|
|
10
|
+
|
|
11
|
+
async function loadFileList() {
|
|
12
|
+
const selector = document.getElementById('file-selector');
|
|
13
|
+
if (!selector) return;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetch('/api/files');
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
selector.innerHTML = '<option value="">No files found</option>';
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const data = await response.json();
|
|
23
|
+
const files = data.files || [];
|
|
24
|
+
currentFilePath = data.current_file;
|
|
25
|
+
|
|
26
|
+
if (files.length === 0) {
|
|
27
|
+
selector.innerHTML = '<option value="">(No recipe files in directory)</option>';
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Build options
|
|
32
|
+
let optionsHtml = '';
|
|
33
|
+
if (!currentFilePath) {
|
|
34
|
+
optionsHtml += '<option value="" selected>(Unsaved figure)</option>';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
files.forEach(file => {
|
|
38
|
+
const isCurrent = file.is_current;
|
|
39
|
+
const icon = file.has_image ? '📊 ' : '📄 ';
|
|
40
|
+
const selected = isCurrent ? ' selected' : '';
|
|
41
|
+
optionsHtml += `<option value="${file.path}"${selected}>${icon}${file.name}</option>`;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
selector.innerHTML = optionsHtml;
|
|
45
|
+
|
|
46
|
+
console.log('[FileSwitcher] Loaded', files.length, 'files');
|
|
47
|
+
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error('[FileSwitcher] Error loading files:', error);
|
|
50
|
+
selector.innerHTML = '<option value="">Error loading files</option>';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function switchToFile(filePath) {
|
|
55
|
+
if (!filePath || filePath === currentFilePath) return;
|
|
56
|
+
|
|
57
|
+
showToast('Loading figure...', 'info');
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch('/api/switch', {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: { 'Content-Type': 'application/json' },
|
|
63
|
+
body: JSON.stringify({ path: filePath })
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
const error = await response.json();
|
|
68
|
+
throw new Error(error.error || 'Failed to switch file');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const data = await response.json();
|
|
72
|
+
|
|
73
|
+
// Update preview image
|
|
74
|
+
const img = document.getElementById('preview-image');
|
|
75
|
+
if (img && data.image) {
|
|
76
|
+
img.src = 'data:image/png;base64,' + data.image;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Update bboxes
|
|
80
|
+
if (data.bboxes) {
|
|
81
|
+
window.currentBboxes = data.bboxes;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Update color map for hitmap
|
|
85
|
+
if (data.color_map) {
|
|
86
|
+
window.currentColorMap = data.color_map;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Update current file path
|
|
90
|
+
currentFilePath = filePath;
|
|
91
|
+
|
|
92
|
+
// Clear selection
|
|
93
|
+
clearSelection();
|
|
94
|
+
document.getElementById('selected-element-panel')?.style.setProperty('display', 'none');
|
|
95
|
+
|
|
96
|
+
showToast('Loaded: ' + filePath, 'success');
|
|
97
|
+
console.log('[FileSwitcher] Switched to:', filePath);
|
|
98
|
+
|
|
99
|
+
// Reload file list to update selection state
|
|
100
|
+
loadFileList();
|
|
101
|
+
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error('[FileSwitcher] Error switching file:', error);
|
|
104
|
+
showToast('Error: ' + error.message, 'error');
|
|
105
|
+
// Revert selector
|
|
106
|
+
loadFileList();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function createNewFigure() {
|
|
111
|
+
showToast('Creating new figure...', 'info');
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch('/api/new', {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: { 'Content-Type': 'application/json' }
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
const error = await response.json();
|
|
121
|
+
throw new Error(error.error || 'Failed to create new figure');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const data = await response.json();
|
|
125
|
+
|
|
126
|
+
// Update preview image
|
|
127
|
+
const img = document.getElementById('preview-image');
|
|
128
|
+
if (img && data.image) {
|
|
129
|
+
img.src = 'data:image/png;base64,' + data.image;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Update bboxes
|
|
133
|
+
if (data.bboxes) {
|
|
134
|
+
window.currentBboxes = data.bboxes;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Update color map for hitmap
|
|
138
|
+
if (data.color_map) {
|
|
139
|
+
window.currentColorMap = data.color_map;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Clear current file path (unsaved figure)
|
|
143
|
+
currentFilePath = null;
|
|
144
|
+
|
|
145
|
+
// Clear selection
|
|
146
|
+
if (typeof clearSelection === 'function') {
|
|
147
|
+
clearSelection();
|
|
148
|
+
}
|
|
149
|
+
const selectedPanel = document.getElementById('selected-element-panel');
|
|
150
|
+
if (selectedPanel) selectedPanel.style.display = 'none';
|
|
151
|
+
|
|
152
|
+
showToast('New blank figure created', 'success');
|
|
153
|
+
console.log('[FileSwitcher] Created new blank figure');
|
|
154
|
+
|
|
155
|
+
// Reload file list to show (Unsaved figure)
|
|
156
|
+
loadFileList();
|
|
157
|
+
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error('[FileSwitcher] Error creating new figure:', error);
|
|
160
|
+
showToast('Error: ' + error.message, 'error');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function initFileSwitcher() {
|
|
165
|
+
const selector = document.getElementById('file-selector');
|
|
166
|
+
const newBtn = document.getElementById('btn-new-figure');
|
|
167
|
+
|
|
168
|
+
if (selector) {
|
|
169
|
+
selector.addEventListener('change', (e) => {
|
|
170
|
+
const filePath = e.target.value;
|
|
171
|
+
if (filePath) {
|
|
172
|
+
switchToFile(filePath);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (newBtn) {
|
|
178
|
+
newBtn.addEventListener('click', createNewFigure);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Load file list on init
|
|
182
|
+
loadFileList();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Initialize file switcher after DOM is ready
|
|
186
|
+
if (document.readyState === 'loading') {
|
|
187
|
+
document.addEventListener('DOMContentLoaded', initFileSwitcher);
|
|
188
|
+
} else {
|
|
189
|
+
initFileSwitcher();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
console.log('[FileSwitcher] Loaded - Use dropdown to switch figures');
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
__all__ = ["SCRIPTS_FILES"]
|