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,279 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Panel position JavaScript for the figure editor.
|
|
4
|
+
|
|
5
|
+
This module contains the JavaScript code for:
|
|
6
|
+
- Loading and displaying panel positions
|
|
7
|
+
- Updating panel positions via numerical inputs
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
SCRIPTS_PANEL_POSITION = r"""
|
|
11
|
+
// ===== PANEL POSITION (mm, upper-left origin) =====
|
|
12
|
+
|
|
13
|
+
let panelPositions = {};
|
|
14
|
+
let figSize = { width_mm: 0, height_mm: 0 };
|
|
15
|
+
|
|
16
|
+
// Load panel positions from server
|
|
17
|
+
async function loadPanelPositions() {
|
|
18
|
+
try {
|
|
19
|
+
const response = await fetch('/get_axes_positions');
|
|
20
|
+
const data = await response.json();
|
|
21
|
+
|
|
22
|
+
// Extract figure size
|
|
23
|
+
if (data._figsize) {
|
|
24
|
+
figSize = data._figsize;
|
|
25
|
+
delete data._figsize;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
panelPositions = data;
|
|
29
|
+
updatePanelSelector();
|
|
30
|
+
updatePanelPositionInputs();
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Failed to load panel positions:', error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Update panel selector dropdown
|
|
37
|
+
function updatePanelSelector() {
|
|
38
|
+
const selector = document.getElementById('panel_selector');
|
|
39
|
+
if (!selector) return;
|
|
40
|
+
|
|
41
|
+
selector.innerHTML = '';
|
|
42
|
+
const axKeys = Object.keys(panelPositions).sort();
|
|
43
|
+
|
|
44
|
+
axKeys.forEach((key, index) => {
|
|
45
|
+
const opt = document.createElement('option');
|
|
46
|
+
opt.value = index;
|
|
47
|
+
// Show panel label (A, B, C...) if available
|
|
48
|
+
const label = String.fromCharCode(65 + index); // A, B, C...
|
|
49
|
+
opt.textContent = `Panel ${label} (${key})`;
|
|
50
|
+
selector.appendChild(opt);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Add change event listener - mark as explicitly selected on dropdown change
|
|
54
|
+
selector.addEventListener('change', () => {
|
|
55
|
+
panelExplicitlySelected = true;
|
|
56
|
+
updatePanelPositionInputs();
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let panelExplicitlySelected = false;
|
|
61
|
+
|
|
62
|
+
// Update position input fields based on selected panel
|
|
63
|
+
function updatePanelPositionInputs(showHighlight = true) {
|
|
64
|
+
const selector = document.getElementById('panel_selector');
|
|
65
|
+
if (!selector) return;
|
|
66
|
+
|
|
67
|
+
const axIndex = parseInt(selector.value, 10);
|
|
68
|
+
const axKey = Object.keys(panelPositions).sort()[axIndex];
|
|
69
|
+
const pos = panelPositions[axKey];
|
|
70
|
+
|
|
71
|
+
if (!pos) return;
|
|
72
|
+
|
|
73
|
+
const leftInput = document.getElementById('panel_left');
|
|
74
|
+
const topInput = document.getElementById('panel_top');
|
|
75
|
+
const widthInput = document.getElementById('panel_width');
|
|
76
|
+
const heightInput = document.getElementById('panel_height');
|
|
77
|
+
|
|
78
|
+
// Values are already in mm from server
|
|
79
|
+
if (leftInput) leftInput.value = pos.left;
|
|
80
|
+
if (topInput) topInput.value = pos.top;
|
|
81
|
+
if (widthInput) widthInput.value = pos.width;
|
|
82
|
+
if (heightInput) heightInput.value = pos.height;
|
|
83
|
+
|
|
84
|
+
// Only draw highlight if explicitly selected (not on initial load)
|
|
85
|
+
if (showHighlight && panelExplicitlySelected) {
|
|
86
|
+
drawPanelSelectionHighlight(pos);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Draw visual highlight around selected panel
|
|
91
|
+
function drawPanelSelectionHighlight(pos) {
|
|
92
|
+
const overlay = document.getElementById('selection-overlay');
|
|
93
|
+
if (!overlay) return;
|
|
94
|
+
|
|
95
|
+
// Clear previous panel highlight (but keep element selections)
|
|
96
|
+
const existingHighlight = document.getElementById('panel-selection-highlight');
|
|
97
|
+
if (existingHighlight) existingHighlight.remove();
|
|
98
|
+
|
|
99
|
+
const img = document.getElementById('preview-image');
|
|
100
|
+
if (!img || !img.naturalWidth || !img.naturalHeight) return;
|
|
101
|
+
|
|
102
|
+
// Ensure viewBox is set
|
|
103
|
+
overlay.setAttribute('viewBox', `0 0 ${img.naturalWidth} ${img.naturalHeight}`);
|
|
104
|
+
overlay.style.width = `${img.naturalWidth}px`;
|
|
105
|
+
overlay.style.height = `${img.naturalHeight}px`;
|
|
106
|
+
|
|
107
|
+
// Convert mm coords (upper-left origin) to image pixel coords
|
|
108
|
+
// pos.left, pos.top, pos.width, pos.height are in mm
|
|
109
|
+
const scaleX = img.naturalWidth / figSize.width_mm;
|
|
110
|
+
const scaleY = img.naturalHeight / figSize.height_mm;
|
|
111
|
+
|
|
112
|
+
const x = pos.left * scaleX;
|
|
113
|
+
const y = pos.top * scaleY; // Already in upper-left origin
|
|
114
|
+
const width = pos.width * scaleX;
|
|
115
|
+
const height = pos.height * scaleY;
|
|
116
|
+
|
|
117
|
+
// Create highlight rectangle
|
|
118
|
+
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
119
|
+
rect.id = 'panel-selection-highlight';
|
|
120
|
+
rect.setAttribute('x', x);
|
|
121
|
+
rect.setAttribute('y', y);
|
|
122
|
+
rect.setAttribute('width', width);
|
|
123
|
+
rect.setAttribute('height', height);
|
|
124
|
+
rect.setAttribute('fill', 'none');
|
|
125
|
+
rect.setAttribute('stroke', '#f59e0b');
|
|
126
|
+
rect.setAttribute('stroke-width', '3');
|
|
127
|
+
rect.setAttribute('stroke-dasharray', '8,4');
|
|
128
|
+
rect.style.pointerEvents = 'none';
|
|
129
|
+
|
|
130
|
+
overlay.appendChild(rect);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Clear panel selection highlight
|
|
134
|
+
function clearPanelSelectionHighlight() {
|
|
135
|
+
const existingHighlight = document.getElementById('panel-selection-highlight');
|
|
136
|
+
if (existingHighlight) existingHighlight.remove();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Select panel by index (called when clicking on axes in canvas)
|
|
140
|
+
function selectPanelByIndex(axIndex) {
|
|
141
|
+
const selector = document.getElementById('panel_selector');
|
|
142
|
+
if (!selector) return;
|
|
143
|
+
|
|
144
|
+
// Mark as explicitly selected
|
|
145
|
+
panelExplicitlySelected = true;
|
|
146
|
+
|
|
147
|
+
// Update dropdown selection
|
|
148
|
+
selector.value = axIndex;
|
|
149
|
+
|
|
150
|
+
// Update inputs and highlight
|
|
151
|
+
updatePanelPositionInputs();
|
|
152
|
+
|
|
153
|
+
// Switch to Axis tab
|
|
154
|
+
switchTab('axis');
|
|
155
|
+
|
|
156
|
+
console.log('Selected panel', axIndex);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Find panel index from axes key (e.g., "ax_0" -> 0)
|
|
160
|
+
function getPanelIndexFromKey(key) {
|
|
161
|
+
if (!key) return null;
|
|
162
|
+
|
|
163
|
+
// Handle "ax_N" format
|
|
164
|
+
const match = key.match(/ax_(\d+)/);
|
|
165
|
+
if (match) {
|
|
166
|
+
return parseInt(match[1], 10);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Handle axes type elements - find by checking bboxes
|
|
170
|
+
const axKeys = Object.keys(panelPositions).sort();
|
|
171
|
+
for (let i = 0; i < axKeys.length; i++) {
|
|
172
|
+
if (axKeys[i] === key) {
|
|
173
|
+
return i;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Apply panel position changes
|
|
181
|
+
async function applyPanelPosition() {
|
|
182
|
+
const selector = document.getElementById('panel_selector');
|
|
183
|
+
const leftInput = document.getElementById('panel_left');
|
|
184
|
+
const topInput = document.getElementById('panel_top');
|
|
185
|
+
const widthInput = document.getElementById('panel_width');
|
|
186
|
+
const heightInput = document.getElementById('panel_height');
|
|
187
|
+
|
|
188
|
+
if (!selector || !leftInput || !topInput || !widthInput || !heightInput) {
|
|
189
|
+
console.error('Panel position inputs not found');
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const axIndex = parseInt(selector.value, 10);
|
|
194
|
+
const left = parseFloat(leftInput.value);
|
|
195
|
+
const top = parseFloat(topInput.value);
|
|
196
|
+
const width = parseFloat(widthInput.value);
|
|
197
|
+
const height = parseFloat(heightInput.value);
|
|
198
|
+
|
|
199
|
+
// Validate values (mm, must be positive and within figure bounds)
|
|
200
|
+
if ([left, top, width, height].some(v => isNaN(v) || v < 0)) {
|
|
201
|
+
alert('Position values must be positive numbers in mm');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (left + width > figSize.width_mm || top + height > figSize.height_mm) {
|
|
206
|
+
alert(`Panel extends beyond figure bounds (${figSize.width_mm.toFixed(1)} x ${figSize.height_mm.toFixed(1)} mm)`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
document.body.classList.add('loading');
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const response = await fetch('/update_axes_position', {
|
|
214
|
+
method: 'POST',
|
|
215
|
+
headers: { 'Content-Type': 'application/json' },
|
|
216
|
+
body: JSON.stringify({
|
|
217
|
+
ax_index: axIndex,
|
|
218
|
+
left: left,
|
|
219
|
+
top: top,
|
|
220
|
+
width: width,
|
|
221
|
+
height: height
|
|
222
|
+
})
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const data = await response.json();
|
|
226
|
+
|
|
227
|
+
if (data.success) {
|
|
228
|
+
// Update preview image and wait for it to load
|
|
229
|
+
const img = document.getElementById('preview-image');
|
|
230
|
+
if (img) {
|
|
231
|
+
await new Promise((resolve) => {
|
|
232
|
+
img.onload = resolve;
|
|
233
|
+
img.src = 'data:image/png;base64,' + data.image;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Update image size
|
|
238
|
+
if (data.img_size) {
|
|
239
|
+
currentImgWidth = data.img_size.width;
|
|
240
|
+
currentImgHeight = data.img_size.height;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Update bboxes
|
|
244
|
+
currentBboxes = data.bboxes;
|
|
245
|
+
loadHitmap();
|
|
246
|
+
updateHitRegions();
|
|
247
|
+
|
|
248
|
+
// Reload positions to reflect any adjustments (now with correct image dimensions)
|
|
249
|
+
await loadPanelPositions();
|
|
250
|
+
|
|
251
|
+
console.log('Panel position updated successfully');
|
|
252
|
+
} else {
|
|
253
|
+
alert('Failed to update position: ' + data.error);
|
|
254
|
+
}
|
|
255
|
+
} catch (error) {
|
|
256
|
+
alert('Failed to update position: ' + error.message);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
document.body.classList.remove('loading');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Initialize panel position controls
|
|
263
|
+
function initPanelPositionControls() {
|
|
264
|
+
const applyBtn = document.getElementById('apply_panel_position');
|
|
265
|
+
if (applyBtn) {
|
|
266
|
+
applyBtn.addEventListener('click', applyPanelPosition);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Load initial positions
|
|
270
|
+
loadPanelPositions();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Call initialization on DOMContentLoaded
|
|
274
|
+
document.addEventListener('DOMContentLoaded', initPanelPositionControls);
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
__all__ = ["SCRIPTS_PANEL_POSITION"]
|
|
278
|
+
|
|
279
|
+
# EOF
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Selection drawing and property synchronization JavaScript.
|
|
4
|
+
|
|
5
|
+
This module contains the JavaScript code for:
|
|
6
|
+
- Drawing selection overlays (polylines, circles, rectangles)
|
|
7
|
+
- Clearing selection state
|
|
8
|
+
- Syncing properties panel to selected element
|
|
9
|
+
- Updating element property highlights
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
SCRIPTS_SELECTION = """
|
|
13
|
+
// ===== SELECTION AND PROPERTY SYNC =====
|
|
14
|
+
|
|
15
|
+
// Clear current selection
|
|
16
|
+
function clearSelection() {
|
|
17
|
+
selectedElement = null;
|
|
18
|
+
clearSelectionOverlay();
|
|
19
|
+
|
|
20
|
+
// Clear section and field highlights
|
|
21
|
+
document.querySelectorAll('.section-highlighted').forEach(s => s.classList.remove('section-highlighted'));
|
|
22
|
+
document.querySelectorAll('.field-highlighted').forEach(f => f.classList.remove('field-highlighted'));
|
|
23
|
+
|
|
24
|
+
// Switch back to Figure tab when nothing selected
|
|
25
|
+
switchTab('figure');
|
|
26
|
+
|
|
27
|
+
// Update hint and show all if in filter mode
|
|
28
|
+
const hint = document.getElementById('selection-hint');
|
|
29
|
+
if (hint && viewMode === 'selected') {
|
|
30
|
+
hint.textContent = '';
|
|
31
|
+
hint.style.color = '';
|
|
32
|
+
showAllProperties();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Draw selection shape(s) - handles lines, scatter, and rectangles
|
|
37
|
+
function drawSelection(key) {
|
|
38
|
+
const overlay = document.getElementById('selection-overlay');
|
|
39
|
+
overlay.innerHTML = '';
|
|
40
|
+
|
|
41
|
+
const img = document.getElementById('preview-image');
|
|
42
|
+
if (!img.naturalWidth || !img.naturalHeight) return;
|
|
43
|
+
|
|
44
|
+
// Set SVG viewBox to match natural image size
|
|
45
|
+
overlay.setAttribute('viewBox', `0 0 ${img.naturalWidth} ${img.naturalHeight}`);
|
|
46
|
+
overlay.style.width = `${img.naturalWidth}px`;
|
|
47
|
+
overlay.style.height = `${img.naturalHeight}px`;
|
|
48
|
+
|
|
49
|
+
const scaleX = 1.0;
|
|
50
|
+
const scaleY = 1.0;
|
|
51
|
+
const offsetX = 0;
|
|
52
|
+
const offsetY = 0;
|
|
53
|
+
|
|
54
|
+
// Determine which elements to highlight
|
|
55
|
+
let elementsToHighlight = [key];
|
|
56
|
+
if (selectedElement && selectedElement.groupElements) {
|
|
57
|
+
elementsToHighlight = selectedElement.groupElements.map(e => e.key);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Draw selection for each element
|
|
61
|
+
for (const elemKey of elementsToHighlight) {
|
|
62
|
+
const bbox = currentBboxes[elemKey];
|
|
63
|
+
if (!bbox) continue;
|
|
64
|
+
|
|
65
|
+
const elementColor = bbox.original_color || '#2563eb';
|
|
66
|
+
const isPrimary = elemKey === key;
|
|
67
|
+
|
|
68
|
+
if (bbox.type === 'line' && bbox.points && bbox.points.length > 1) {
|
|
69
|
+
_drawLineSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY);
|
|
70
|
+
} else if (bbox.type === 'scatter' && bbox.points && bbox.points.length > 0) {
|
|
71
|
+
_drawScatterSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY);
|
|
72
|
+
} else {
|
|
73
|
+
_drawRectSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Helper: Draw line selection (polyline)
|
|
79
|
+
function _drawLineSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY) {
|
|
80
|
+
const points = bbox.points.map(pt => {
|
|
81
|
+
const x = offsetX + pt[0] * scaleX;
|
|
82
|
+
const y = offsetY + pt[1] * scaleY;
|
|
83
|
+
return `${x},${y}`;
|
|
84
|
+
}).join(' ');
|
|
85
|
+
|
|
86
|
+
const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
|
|
87
|
+
polyline.setAttribute('points', points);
|
|
88
|
+
polyline.setAttribute('class', 'selection-polyline');
|
|
89
|
+
polyline.style.setProperty('--element-color', elementColor);
|
|
90
|
+
if (isPrimary) {
|
|
91
|
+
polyline.style.strokeWidth = '10';
|
|
92
|
+
polyline.style.strokeOpacity = '0.6';
|
|
93
|
+
}
|
|
94
|
+
overlay.appendChild(polyline);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Helper: Draw scatter selection (circles)
|
|
98
|
+
function _drawScatterSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY) {
|
|
99
|
+
bbox.points.forEach(pt => {
|
|
100
|
+
const cx = offsetX + pt[0] * scaleX;
|
|
101
|
+
const cy = offsetY + pt[1] * scaleY;
|
|
102
|
+
|
|
103
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
104
|
+
circle.setAttribute('cx', cx);
|
|
105
|
+
circle.setAttribute('cy', cy);
|
|
106
|
+
circle.setAttribute('r', isPrimary ? 4 : 3);
|
|
107
|
+
circle.setAttribute('class', 'selection-circle-subtle');
|
|
108
|
+
circle.style.setProperty('--element-color', elementColor);
|
|
109
|
+
overlay.appendChild(circle);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Helper: Draw rectangle selection
|
|
114
|
+
function _drawRectSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY) {
|
|
115
|
+
const x = offsetX + bbox.x * scaleX;
|
|
116
|
+
const y = offsetY + bbox.y * scaleY;
|
|
117
|
+
const width = bbox.width * scaleX;
|
|
118
|
+
const height = bbox.height * scaleY;
|
|
119
|
+
|
|
120
|
+
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
121
|
+
rect.setAttribute('x', x);
|
|
122
|
+
rect.setAttribute('y', y);
|
|
123
|
+
rect.setAttribute('width', Math.max(width, 2));
|
|
124
|
+
rect.setAttribute('height', Math.max(height, 2));
|
|
125
|
+
rect.setAttribute('class', 'selection-rect');
|
|
126
|
+
rect.style.setProperty('--element-color', elementColor);
|
|
127
|
+
|
|
128
|
+
if (isPrimary) {
|
|
129
|
+
rect.classList.add('selection-primary');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
overlay.appendChild(rect);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Clear selection overlay
|
|
136
|
+
function clearSelectionOverlay() {
|
|
137
|
+
document.getElementById('selection-overlay').innerHTML = '';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Sync properties panel to selected element
|
|
141
|
+
function syncPropertiesToElement(element) {
|
|
142
|
+
// Always show dynamic call properties for the selected element
|
|
143
|
+
showDynamicCallProperties(element);
|
|
144
|
+
|
|
145
|
+
// In 'selected' mode, only show call properties (no section highlighting)
|
|
146
|
+
if (viewMode === 'selected') {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Map element types to section IDs (for 'all' mode)
|
|
151
|
+
const sectionMap = {
|
|
152
|
+
'axes': 'section-dimensions',
|
|
153
|
+
'line': 'section-lines',
|
|
154
|
+
'scatter': 'section-markers',
|
|
155
|
+
'bar': 'section-lines',
|
|
156
|
+
'fill': 'section-lines',
|
|
157
|
+
'boxplot': 'section-boxplot',
|
|
158
|
+
'violin': 'section-violin',
|
|
159
|
+
'title': 'section-fonts',
|
|
160
|
+
'xlabel': 'section-fonts',
|
|
161
|
+
'ylabel': 'section-fonts',
|
|
162
|
+
'xticks': 'section-ticks',
|
|
163
|
+
'yticks': 'section-ticks',
|
|
164
|
+
'legend': 'section-legend',
|
|
165
|
+
'spine': 'section-dimensions',
|
|
166
|
+
'contour': 'section-dimensions',
|
|
167
|
+
'image': 'section-dimensions',
|
|
168
|
+
'pie': 'section-dimensions',
|
|
169
|
+
'hist': 'section-lines',
|
|
170
|
+
'quiver': 'section-lines',
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const sectionId = sectionMap[element.type] || 'section-dimensions';
|
|
174
|
+
|
|
175
|
+
// Close all sections and remove highlights (accordion behavior)
|
|
176
|
+
document.querySelectorAll('.section').forEach(section => {
|
|
177
|
+
section.classList.remove('section-highlighted');
|
|
178
|
+
if (section.id && section.id !== 'section-download') {
|
|
179
|
+
section.removeAttribute('open');
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Find and highlight the relevant section
|
|
184
|
+
const section = document.getElementById(sectionId);
|
|
185
|
+
if (section) {
|
|
186
|
+
section.setAttribute('open', '');
|
|
187
|
+
section.classList.add('section-highlighted');
|
|
188
|
+
setTimeout(() => {
|
|
189
|
+
section.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
190
|
+
}, 50);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
updateElementProperties(element);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Update property values for selected element
|
|
197
|
+
function updateElementProperties(element) {
|
|
198
|
+
// Clear previous field highlights
|
|
199
|
+
document.querySelectorAll('.form-row').forEach(row => {
|
|
200
|
+
row.classList.remove('field-highlighted');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Map element types to relevant form field IDs
|
|
204
|
+
const fieldMap = {
|
|
205
|
+
'line': ['lines_trace_mm', 'lines_errorbar_mm', 'lines_errorbar_cap_mm'],
|
|
206
|
+
'scatter': ['markers_size_mm', 'markers_scatter_mm', 'markers_edge_width_mm'],
|
|
207
|
+
'bar': ['lines_trace_mm'],
|
|
208
|
+
'fill': ['lines_trace_mm'],
|
|
209
|
+
'boxplot': ['lines_trace_mm', 'markers_flier_mm', 'boxplot_median_color'],
|
|
210
|
+
'violin': ['lines_trace_mm'],
|
|
211
|
+
'title': ['fonts_title_pt', 'fonts_family'],
|
|
212
|
+
'xlabel': ['fonts_axis_label_pt', 'fonts_family'],
|
|
213
|
+
'ylabel': ['fonts_axis_label_pt', 'fonts_family'],
|
|
214
|
+
'xticks': ['fonts_tick_label_pt', 'ticks_length_mm', 'ticks_direction'],
|
|
215
|
+
'yticks': ['fonts_tick_label_pt', 'ticks_length_mm', 'ticks_direction'],
|
|
216
|
+
'legend': ['fonts_legend_pt', 'legend_frameon', 'legend_loc'],
|
|
217
|
+
'spine': ['axes_thickness_mm'],
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const relevantFields = fieldMap[element.type] || [];
|
|
221
|
+
|
|
222
|
+
// Highlight relevant form rows
|
|
223
|
+
relevantFields.forEach(fieldId => {
|
|
224
|
+
const row = document.querySelector(`[data-field="${fieldId}"]`);
|
|
225
|
+
if (row) {
|
|
226
|
+
row.classList.add('field-highlighted');
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Show dynamic call properties for this element
|
|
231
|
+
showDynamicCallProperties(element);
|
|
232
|
+
}
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
__all__ = ["SCRIPTS_SELECTION"]
|
|
236
|
+
|
|
237
|
+
# EOF
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tab navigation JavaScript for the figure editor.
|
|
4
|
+
|
|
5
|
+
This module contains the JavaScript code for:
|
|
6
|
+
- Tab switching (Figure/Axis/Element)
|
|
7
|
+
- Auto-switching based on selected element
|
|
8
|
+
- Tab hints management
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
SCRIPTS_TABS = """
|
|
12
|
+
// ===== TAB NAVIGATION =====
|
|
13
|
+
|
|
14
|
+
// Current active tab
|
|
15
|
+
let currentTab = 'figure';
|
|
16
|
+
|
|
17
|
+
// Element type to tab mapping
|
|
18
|
+
const AXIS_TYPES = ['title', 'xlabel', 'ylabel', 'suptitle', 'supxlabel', 'supylabel', 'legend'];
|
|
19
|
+
const ELEMENT_TYPES = ['line', 'scatter', 'bar', 'hist', 'fill', 'boxplot', 'violin', 'image', 'linecollection', 'quiver', 'pie', 'contour', 'specgram'];
|
|
20
|
+
|
|
21
|
+
// Switch between Figure/Axis/Element tabs
|
|
22
|
+
function switchTab(tabName) {
|
|
23
|
+
currentTab = tabName;
|
|
24
|
+
|
|
25
|
+
// Update tab buttons
|
|
26
|
+
document.querySelectorAll('.tab-btn').forEach(btn => {
|
|
27
|
+
btn.classList.toggle('active', btn.id === `tab-${tabName}`);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Update tab content
|
|
31
|
+
document.querySelectorAll('.tab-content').forEach(content => {
|
|
32
|
+
content.classList.toggle('active', content.id === `tab-content-${tabName}`);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Update hints based on selection state
|
|
36
|
+
updateTabHints();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Get appropriate tab for element type
|
|
40
|
+
function getTabForElementType(elementType) {
|
|
41
|
+
if (!elementType) return 'figure';
|
|
42
|
+
if (AXIS_TYPES.includes(elementType)) return 'axis';
|
|
43
|
+
if (ELEMENT_TYPES.includes(elementType)) return 'element';
|
|
44
|
+
return 'figure';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Auto-switch to appropriate tab based on selected element
|
|
48
|
+
function autoSwitchTab(elementType) {
|
|
49
|
+
const targetTab = getTabForElementType(elementType);
|
|
50
|
+
if (targetTab !== currentTab) {
|
|
51
|
+
switchTab(targetTab);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Update tab hints based on current state
|
|
56
|
+
function updateTabHints() {
|
|
57
|
+
const axisHint = document.getElementById('axis-tab-hint');
|
|
58
|
+
const elementHint = document.getElementById('element-tab-hint');
|
|
59
|
+
const elementPanel = document.getElementById('selected-element-panel');
|
|
60
|
+
const dynamicProps = document.getElementById('dynamic-call-properties');
|
|
61
|
+
|
|
62
|
+
if (currentTab === 'axis') {
|
|
63
|
+
if (selectedElement && AXIS_TYPES.includes(selectedElement.type)) {
|
|
64
|
+
if (axisHint) axisHint.style.display = 'none';
|
|
65
|
+
} else {
|
|
66
|
+
if (axisHint) axisHint.style.display = 'block';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (currentTab === 'element') {
|
|
71
|
+
if (selectedElement && ELEMENT_TYPES.includes(selectedElement.type)) {
|
|
72
|
+
if (elementHint) elementHint.style.display = 'none';
|
|
73
|
+
if (elementPanel) {
|
|
74
|
+
elementPanel.style.display = 'block';
|
|
75
|
+
document.getElementById('element-type-badge').textContent = selectedElement.type;
|
|
76
|
+
document.getElementById('element-name').textContent = selectedElement.label || selectedElement.key;
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
if (elementHint) elementHint.style.display = 'block';
|
|
80
|
+
if (elementPanel) elementPanel.style.display = 'none';
|
|
81
|
+
if (dynamicProps) dynamicProps.style.display = 'none';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
__all__ = ["SCRIPTS_TABS"]
|
|
88
|
+
|
|
89
|
+
# EOF
|