figrecipe 0.6.0__py3-none-any.whl → 0.9.0__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 +161 -1030
- figrecipe/__main__.py +12 -0
- figrecipe/_api/__init__.py +48 -0
- figrecipe/_api/_extract.py +108 -0
- figrecipe/_api/_notebook.py +61 -0
- figrecipe/_api/_panel.py +113 -0
- figrecipe/_api/_save.py +287 -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/_cli/__init__.py +7 -0
- figrecipe/_cli/_compose.py +87 -0
- figrecipe/_cli/_convert.py +117 -0
- figrecipe/_cli/_crop.py +82 -0
- figrecipe/_cli/_edit.py +70 -0
- figrecipe/_cli/_extract.py +128 -0
- figrecipe/_cli/_fonts.py +47 -0
- figrecipe/_cli/_info.py +67 -0
- figrecipe/_cli/_main.py +58 -0
- figrecipe/_cli/_reproduce.py +79 -0
- figrecipe/_cli/_style.py +77 -0
- figrecipe/_cli/_validate.py +66 -0
- figrecipe/_cli/_version.py +50 -0
- figrecipe/_composition/__init__.py +32 -0
- figrecipe/_composition/_alignment.py +452 -0
- figrecipe/_composition/_compose.py +179 -0
- figrecipe/_composition/_import_axes.py +127 -0
- figrecipe/_composition/_visibility.py +125 -0
- figrecipe/_dev/__init__.py +4 -93
- figrecipe/_dev/_plotters.py +76 -0
- figrecipe/_dev/_run_demos.py +56 -0
- figrecipe/_dev/browser/__init__.py +69 -0
- figrecipe/_dev/browser/_audio.py +240 -0
- figrecipe/_dev/browser/_caption.py +356 -0
- figrecipe/_dev/browser/_click_effect.py +146 -0
- figrecipe/_dev/browser/_cursor.py +196 -0
- figrecipe/_dev/browser/_highlight.py +105 -0
- figrecipe/_dev/browser/_narration.py +237 -0
- figrecipe/_dev/browser/_recorder.py +446 -0
- figrecipe/_dev/browser/_utils.py +178 -0
- figrecipe/_dev/browser/_video_trim/__init__.py +152 -0
- figrecipe/_dev/browser/_video_trim/_detection.py +223 -0
- figrecipe/_dev/browser/_video_trim/_markers.py +140 -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 +61 -13
- 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 +402 -0
- figrecipe/_editor/_bbox/_extract_axes.py +370 -0
- figrecipe/_editor/_bbox/_extract_text.py +466 -0
- figrecipe/_editor/_bbox/_lines.py +173 -0
- figrecipe/_editor/_bbox/_transforms.py +146 -0
- figrecipe/_editor/_call_overrides.py +183 -0
- figrecipe/_editor/_datatable_plot_handlers.py +249 -0
- figrecipe/_editor/_figure_layout.py +211 -0
- figrecipe/_editor/_flask_app.py +200 -1030
- figrecipe/_editor/_helpers.py +251 -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 +194 -0
- figrecipe/_editor/_hitmap/_restore.py +154 -0
- figrecipe/_editor/_hitmap_main.py +182 -0
- figrecipe/_editor/_overrides.py +4 -1
- figrecipe/_editor/_plot_types_registry.py +190 -0
- figrecipe/_editor/_preferences.py +135 -0
- figrecipe/_editor/_render_overrides.py +507 -0
- figrecipe/_editor/_renderer.py +81 -186
- figrecipe/_editor/_routes_annotation.py +114 -0
- figrecipe/_editor/_routes_axis.py +482 -0
- figrecipe/_editor/_routes_captions.py +130 -0
- figrecipe/_editor/_routes_composition.py +270 -0
- figrecipe/_editor/_routes_core.py +126 -0
- figrecipe/_editor/_routes_datatable.py +364 -0
- figrecipe/_editor/_routes_element.py +335 -0
- figrecipe/_editor/_routes_files.py +443 -0
- figrecipe/_editor/_routes_image.py +200 -0
- figrecipe/_editor/_routes_snapshot.py +94 -0
- figrecipe/_editor/_routes_style.py +243 -0
- figrecipe/_editor/_templates/__init__.py +116 -1
- figrecipe/_editor/_templates/_html.py +154 -64
- figrecipe/_editor/_templates/_html_components/__init__.py +13 -0
- figrecipe/_editor/_templates/_html_components/_composition_toolbar.py +79 -0
- figrecipe/_editor/_templates/_html_components/_file_browser.py +41 -0
- figrecipe/_editor/_templates/_html_datatable.py +92 -0
- figrecipe/_editor/_templates/_scripts/__init__.py +178 -0
- figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
- figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
- figrecipe/_editor/_templates/_scripts/_api.py +228 -0
- figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
- figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
- figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
- figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
- figrecipe/_editor/_templates/_scripts/_core.py +493 -0
- figrecipe/_editor/_templates/_scripts/_datatable/__init__.py +59 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_cell_edit.py +97 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_clipboard.py +164 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_context_menu.py +221 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_core.py +150 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_editable.py +511 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_import.py +161 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_plot.py +261 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_selection.py +438 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_table.py +256 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_tabs.py +354 -0
- figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
- figrecipe/_editor/_templates/_scripts/_element_editor.py +325 -0
- figrecipe/_editor/_templates/_scripts/_files.py +429 -0
- figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
- figrecipe/_editor/_templates/_scripts/_hitmap.py +512 -0
- figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
- figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
- figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
- figrecipe/_editor/_templates/_scripts/_legend_drag.py +270 -0
- figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
- figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
- figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag.py +505 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
- figrecipe/_editor/_templates/_scripts/_panel_position.py +463 -0
- figrecipe/_editor/_templates/_scripts/_panel_resize.py +230 -0
- figrecipe/_editor/_templates/_scripts/_panel_snap.py +307 -0
- figrecipe/_editor/_templates/_scripts/_region_select.py +255 -0
- figrecipe/_editor/_templates/_scripts/_selection.py +244 -0
- figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
- figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
- figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
- figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
- figrecipe/_editor/_templates/_scripts/_zoom.py +212 -0
- figrecipe/_editor/_templates/_styles/__init__.py +78 -0
- figrecipe/_editor/_templates/_styles/_base.py +111 -0
- figrecipe/_editor/_templates/_styles/_buttons.py +327 -0
- figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
- figrecipe/_editor/_templates/_styles/_composition.py +87 -0
- figrecipe/_editor/_templates/_styles/_controls.py +430 -0
- figrecipe/_editor/_templates/_styles/_datatable/__init__.py +40 -0
- figrecipe/_editor/_templates/_styles/_datatable/_editable.py +203 -0
- figrecipe/_editor/_templates/_styles/_datatable/_panel.py +268 -0
- figrecipe/_editor/_templates/_styles/_datatable/_table.py +479 -0
- figrecipe/_editor/_templates/_styles/_datatable/_toolbar.py +384 -0
- figrecipe/_editor/_templates/_styles/_datatable/_vars.py +123 -0
- figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
- figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
- figrecipe/_editor/_templates/_styles/_forms.py +224 -0
- figrecipe/_editor/_templates/_styles/_hitmap.py +191 -0
- figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
- figrecipe/_editor/_templates/_styles/_labels.py +118 -0
- figrecipe/_editor/_templates/_styles/_modals.py +127 -0
- figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
- figrecipe/_editor/_templates/_styles/_preview.py +430 -0
- figrecipe/_editor/_templates/_styles/_selection.py +73 -0
- figrecipe/_editor/_templates/_styles/_spinner.py +117 -0
- figrecipe/_editor/static/audio/click.mp3 +0 -0
- figrecipe/_editor/static/click.mp3 +0 -0
- figrecipe/_editor/static/icons/favicon.ico +0 -0
- figrecipe/_integrations/__init__.py +17 -0
- figrecipe/_integrations/_scitex_stats.py +298 -0
- figrecipe/_params/_DECORATION_METHODS.py +8 -0
- figrecipe/_recorder.py +63 -109
- figrecipe/_recorder_utils.py +124 -0
- figrecipe/_reproducer/__init__.py +18 -0
- figrecipe/_reproducer/_core.py +509 -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/_utils/__init__.py +3 -0
- figrecipe/_utils/_bundle.py +205 -0
- figrecipe/_wrappers/_axes.py +252 -895
- figrecipe/_wrappers/_axes_helpers.py +136 -0
- figrecipe/_wrappers/_axes_plots.py +418 -0
- figrecipe/_wrappers/_axes_seaborn.py +157 -0
- figrecipe/_wrappers/_caption_generator.py +218 -0
- figrecipe/_wrappers/_figure.py +188 -1
- figrecipe/_wrappers/_panel_labels.py +127 -0
- figrecipe/_wrappers/_plot_helpers.py +143 -0
- figrecipe/_wrappers/_stat_annotation.py +274 -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 +42 -480
- 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 +40 -28
- figrecipe-0.9.0.dist-info/METADATA +427 -0
- figrecipe-0.9.0.dist-info/RECORD +277 -0
- figrecipe-0.9.0.dist-info/entry_points.txt +2 -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/METADATA +0 -394
- 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.9.0.dist-info}/WHEEL +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Annotation drag-to-move JavaScript for the figure editor.
|
|
4
|
+
|
|
5
|
+
This module contains the JavaScript code for:
|
|
6
|
+
- Detecting mousedown on annotation elements (panel labels, text, arrows)
|
|
7
|
+
- Handling drag movement with visual feedback
|
|
8
|
+
- Updating annotation position on drop
|
|
9
|
+
|
|
10
|
+
Annotation coordinates are in axes-relative units (0-1 range).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
SCRIPTS_ANNOTATION_DRAG = """
|
|
14
|
+
// ===== ANNOTATION DRAG-TO-MOVE =====
|
|
15
|
+
|
|
16
|
+
let isDraggingAnnotation = false;
|
|
17
|
+
let annotationDragStartPos = null;
|
|
18
|
+
let annotationDragStartBbox = null;
|
|
19
|
+
let annotationDragOverlay = null;
|
|
20
|
+
let annotationDragLabel = null;
|
|
21
|
+
let annotationKey = null;
|
|
22
|
+
let annotationAxIndex = 0;
|
|
23
|
+
let annotationDragOffset = null; // Offset from click to bbox corner for precise cursor following
|
|
24
|
+
let annotationOriginalRelPos = null; // Original axes-relative position (anchor point)
|
|
25
|
+
let annotationLastSnappedBbox = null; // Last snapped bbox position (used on mouse up)
|
|
26
|
+
|
|
27
|
+
// Track annotation positions for undo support (axes-relative 0-1 coordinates)
|
|
28
|
+
// Key format: "ax{axIndex}_text{textIndex}" or "ax{axIndex}_panel_label0"
|
|
29
|
+
let annotationPositions = {}; // {ax0_text0: {x, y}, ...}
|
|
30
|
+
|
|
31
|
+
// Initialize annotation positions from current bboxes
|
|
32
|
+
function initAnnotationPositions() {
|
|
33
|
+
if (typeof currentBboxes === 'undefined') return;
|
|
34
|
+
|
|
35
|
+
annotationPositions = {};
|
|
36
|
+
for (const [key, bbox] of Object.entries(currentBboxes)) {
|
|
37
|
+
if (isAnnotationElement(key) && bbox.rel_x !== undefined && bbox.rel_y !== undefined) {
|
|
38
|
+
annotationPositions[key] = {
|
|
39
|
+
x: bbox.rel_x,
|
|
40
|
+
y: bbox.rel_y,
|
|
41
|
+
type: bbox.type || 'text'
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
console.log('[AnnotationDrag] Initialized annotation positions:', Object.keys(annotationPositions).length);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Snap configuration for annotations (uses same visual system as panel snap)
|
|
49
|
+
const ANNOTATION_SNAP = {
|
|
50
|
+
enabled: true,
|
|
51
|
+
threshold: 5, // mm - same as panel snap threshold
|
|
52
|
+
magneticZone: 10, // mm - distance where magnetic attraction starts
|
|
53
|
+
snapToEdges: true,
|
|
54
|
+
snapToOtherLabels: true // Snap to other panel labels
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Initialize annotation drag functionality
|
|
58
|
+
function initAnnotationDrag() {
|
|
59
|
+
console.log('[AnnotationDrag] initAnnotationDrag called');
|
|
60
|
+
const zoomContainer = document.getElementById('zoom-container');
|
|
61
|
+
if (!zoomContainer) {
|
|
62
|
+
console.error('[AnnotationDrag] zoom-container not found!');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Create annotation drag overlay element (bounding box)
|
|
67
|
+
annotationDragOverlay = document.createElement('div');
|
|
68
|
+
annotationDragOverlay.id = 'annotation-drag-overlay';
|
|
69
|
+
annotationDragOverlay.style.cssText = `
|
|
70
|
+
position: absolute;
|
|
71
|
+
border: 2px dashed #8b5cf6;
|
|
72
|
+
background: rgba(139, 92, 246, 0.15);
|
|
73
|
+
pointer-events: none;
|
|
74
|
+
display: none;
|
|
75
|
+
z-index: 1001;
|
|
76
|
+
box-shadow: 0 2px 8px rgba(139, 92, 246, 0.3);
|
|
77
|
+
transition: box-shadow 0.1s ease;
|
|
78
|
+
`;
|
|
79
|
+
zoomContainer.appendChild(annotationDragOverlay);
|
|
80
|
+
|
|
81
|
+
// Create label showing text content during drag
|
|
82
|
+
annotationDragLabel = document.createElement('div');
|
|
83
|
+
annotationDragLabel.id = 'annotation-drag-label';
|
|
84
|
+
annotationDragLabel.style.cssText = `
|
|
85
|
+
position: absolute;
|
|
86
|
+
background: #8b5cf6;
|
|
87
|
+
color: white;
|
|
88
|
+
padding: 2px 6px;
|
|
89
|
+
border-radius: 3px;
|
|
90
|
+
font-size: 11px;
|
|
91
|
+
font-weight: bold;
|
|
92
|
+
pointer-events: none;
|
|
93
|
+
display: none;
|
|
94
|
+
z-index: 1002;
|
|
95
|
+
white-space: nowrap;
|
|
96
|
+
`;
|
|
97
|
+
zoomContainer.appendChild(annotationDragLabel);
|
|
98
|
+
|
|
99
|
+
console.log('[AnnotationDrag] Overlay and label created');
|
|
100
|
+
// Note: Snap guides are shared with panel snap (created in initPanelSnap)
|
|
101
|
+
|
|
102
|
+
// Initialize positions after a short delay to ensure bboxes are loaded
|
|
103
|
+
setTimeout(initAnnotationPositions, 500);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check if an element is an annotation (panel_label, text, arrow)
|
|
107
|
+
function isAnnotationElement(key) {
|
|
108
|
+
if (!key) return false;
|
|
109
|
+
return key.includes('_panel_label') ||
|
|
110
|
+
key.includes('_text_') ||
|
|
111
|
+
key.includes('_arrow_');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Handle annotation drag start (called from hitmap click handler)
|
|
115
|
+
function startAnnotationDrag(event, elemKey) {
|
|
116
|
+
console.log('[AnnotationDrag] startAnnotationDrag called for:', elemKey);
|
|
117
|
+
|
|
118
|
+
const img = document.getElementById('preview-image');
|
|
119
|
+
if (!img) return false;
|
|
120
|
+
|
|
121
|
+
const bbox = currentBboxes[elemKey];
|
|
122
|
+
if (!bbox) return false;
|
|
123
|
+
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
event.stopPropagation();
|
|
126
|
+
|
|
127
|
+
// Capture state before drag for undo
|
|
128
|
+
if (typeof pushToHistory === 'function') {
|
|
129
|
+
pushToHistory();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
isDraggingAnnotation = true;
|
|
133
|
+
annotationKey = elemKey;
|
|
134
|
+
annotationDragStartPos = { x: event.clientX, y: event.clientY };
|
|
135
|
+
annotationDragStartBbox = { ...bbox };
|
|
136
|
+
|
|
137
|
+
// Store the original axes-relative position (anchor point, not bbox corner)
|
|
138
|
+
// This is the actual text position used by matplotlib
|
|
139
|
+
annotationOriginalRelPos = {
|
|
140
|
+
x: bbox.rel_x !== undefined ? bbox.rel_x : 0,
|
|
141
|
+
y: bbox.rel_y !== undefined ? bbox.rel_y : 0
|
|
142
|
+
};
|
|
143
|
+
console.log('[AnnotationDrag] Original anchor position (rel):', annotationOriginalRelPos.x.toFixed(3), annotationOriginalRelPos.y.toFixed(3));
|
|
144
|
+
|
|
145
|
+
// Calculate offset from click position to bbox corner for precise cursor following
|
|
146
|
+
// This ensures the overlay follows the cursor at exactly the click point
|
|
147
|
+
const rect = img.getBoundingClientRect();
|
|
148
|
+
const scaleX = rect.width / img.naturalWidth;
|
|
149
|
+
const scaleY = rect.height / img.naturalHeight;
|
|
150
|
+
const bboxScreenX = bbox.x * scaleX;
|
|
151
|
+
const bboxScreenY = bbox.y * scaleY;
|
|
152
|
+
// Offset is the distance from bbox corner to click point (in screen pixels)
|
|
153
|
+
annotationDragOffset = {
|
|
154
|
+
x: event.clientX - rect.left - bboxScreenX,
|
|
155
|
+
y: event.clientY - rect.top - bboxScreenY
|
|
156
|
+
};
|
|
157
|
+
console.log('[AnnotationDrag] Click offset from bbox corner:', annotationDragOffset.x.toFixed(1), annotationDragOffset.y.toFixed(1));
|
|
158
|
+
|
|
159
|
+
// Extract axis index from key (e.g., "ax0_panel_label" -> 0)
|
|
160
|
+
const match = elemKey.match(/ax(\\d+)_/);
|
|
161
|
+
annotationAxIndex = match ? parseInt(match[1], 10) : 0;
|
|
162
|
+
|
|
163
|
+
// Show drag overlay
|
|
164
|
+
if (annotationDragOverlay) {
|
|
165
|
+
updateAnnotationDragOverlay(bbox);
|
|
166
|
+
annotationDragOverlay.style.display = 'block';
|
|
167
|
+
// Add glow effect when dragging starts
|
|
168
|
+
annotationDragOverlay.style.boxShadow = '0 4px 12px rgba(139, 92, 246, 0.5)';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Show label with text content
|
|
172
|
+
if (annotationDragLabel && bbox.text) {
|
|
173
|
+
annotationDragLabel.textContent = bbox.text;
|
|
174
|
+
annotationDragLabel.style.display = 'block';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Add temporary event listeners
|
|
178
|
+
document.addEventListener('mousemove', handleAnnotationDragMove);
|
|
179
|
+
document.addEventListener('mouseup', handleAnnotationDragEnd);
|
|
180
|
+
|
|
181
|
+
document.body.style.cursor = 'move';
|
|
182
|
+
console.log('[AnnotationDrag] Started dragging annotation:', bbox.text || elemKey);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Handle mouse move during annotation drag
|
|
187
|
+
function handleAnnotationDragMove(event) {
|
|
188
|
+
if (!isDraggingAnnotation) return;
|
|
189
|
+
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
|
|
192
|
+
const img = document.getElementById('preview-image');
|
|
193
|
+
if (!img) return;
|
|
194
|
+
|
|
195
|
+
const rect = img.getBoundingClientRect();
|
|
196
|
+
|
|
197
|
+
// Calculate new bbox position directly from cursor position minus offset
|
|
198
|
+
// This ensures the overlay follows the cursor precisely at the click point
|
|
199
|
+
const screenToImgX = img.naturalWidth / rect.width;
|
|
200
|
+
const screenToImgY = img.naturalHeight / rect.height;
|
|
201
|
+
|
|
202
|
+
// Convert current mouse position to image coordinates, accounting for the click offset
|
|
203
|
+
const cursorScreenX = event.clientX - rect.left - annotationDragOffset.x;
|
|
204
|
+
const cursorScreenY = event.clientY - rect.top - annotationDragOffset.y;
|
|
205
|
+
|
|
206
|
+
let newBbox = {
|
|
207
|
+
x: cursorScreenX * screenToImgX,
|
|
208
|
+
y: cursorScreenY * screenToImgY,
|
|
209
|
+
width: annotationDragStartBbox.width,
|
|
210
|
+
height: annotationDragStartBbox.height
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Apply snap and update visual overlay
|
|
214
|
+
const snapResult = applyAnnotationSnap(newBbox, annotationAxIndex);
|
|
215
|
+
updateAnnotationDragOverlay(snapResult.bbox, snapResult.snapped);
|
|
216
|
+
|
|
217
|
+
// Store snapped position for use on mouse up
|
|
218
|
+
annotationLastSnappedBbox = snapResult.bbox;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Update the annotation drag overlay position
|
|
222
|
+
function updateAnnotationDragOverlay(bbox, isSnapped = false) {
|
|
223
|
+
if (!annotationDragOverlay) return;
|
|
224
|
+
|
|
225
|
+
const img = document.getElementById('preview-image');
|
|
226
|
+
if (!img) return;
|
|
227
|
+
|
|
228
|
+
const rect = img.getBoundingClientRect();
|
|
229
|
+
const scaleX = rect.width / img.naturalWidth;
|
|
230
|
+
const scaleY = rect.height / img.naturalHeight;
|
|
231
|
+
|
|
232
|
+
const left = bbox.x * scaleX;
|
|
233
|
+
const top = bbox.y * scaleY;
|
|
234
|
+
const width = bbox.width * scaleX;
|
|
235
|
+
const height = bbox.height * scaleY;
|
|
236
|
+
|
|
237
|
+
annotationDragOverlay.style.left = `${left}px`;
|
|
238
|
+
annotationDragOverlay.style.top = `${top}px`;
|
|
239
|
+
annotationDragOverlay.style.width = `${width}px`;
|
|
240
|
+
annotationDragOverlay.style.height = `${height}px`;
|
|
241
|
+
|
|
242
|
+
// Visual feedback for snap
|
|
243
|
+
if (isSnapped) {
|
|
244
|
+
annotationDragOverlay.style.borderColor = '#22c55e'; // Green when snapped
|
|
245
|
+
annotationDragOverlay.style.boxShadow = '0 4px 12px rgba(34, 197, 94, 0.5)';
|
|
246
|
+
} else {
|
|
247
|
+
annotationDragOverlay.style.borderColor = '#8b5cf6'; // Purple when not snapped
|
|
248
|
+
annotationDragOverlay.style.boxShadow = '0 4px 12px rgba(139, 92, 246, 0.5)';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Update label position (above the overlay)
|
|
252
|
+
if (annotationDragLabel && annotationDragLabel.style.display !== 'none') {
|
|
253
|
+
annotationDragLabel.style.left = `${left}px`;
|
|
254
|
+
annotationDragLabel.style.top = `${top - 20}px`;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Get snap targets from other panel labels (for alignment)
|
|
259
|
+
function getAnnotationSnapTargets(excludeKey) {
|
|
260
|
+
const targets = { h: [], v: [] };
|
|
261
|
+
if (typeof currentBboxes === 'undefined' || !figSize.width_mm) return targets;
|
|
262
|
+
const img = document.getElementById('preview-image');
|
|
263
|
+
if (!img) return targets;
|
|
264
|
+
const pxToMmX = figSize.width_mm / img.naturalWidth;
|
|
265
|
+
const pxToMmY = figSize.height_mm / img.naturalHeight;
|
|
266
|
+
for (const [key, bbox] of Object.entries(currentBboxes)) {
|
|
267
|
+
if (key === excludeKey || !key.includes('_panel_label')) continue;
|
|
268
|
+
const cx = (bbox.x + bbox.width / 2) * pxToMmX;
|
|
269
|
+
const cy = (bbox.y + bbox.height / 2) * pxToMmY;
|
|
270
|
+
targets.v.push({ pos: cx, type: 'label-center' });
|
|
271
|
+
targets.h.push({ pos: cy, type: 'label-center' });
|
|
272
|
+
}
|
|
273
|
+
return targets;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Apply snap using CENTER-TO-CENTER alignment for labels
|
|
277
|
+
// Always snaps to the CLOSEST target within threshold (not the first match)
|
|
278
|
+
function applyAnnotationSnap(newBbox, axIndex) {
|
|
279
|
+
const img = document.getElementById('preview-image');
|
|
280
|
+
const rect = img ? img.getBoundingClientRect() : null;
|
|
281
|
+
if (!ANNOTATION_SNAP.enabled || !img || !figSize.width_mm) {
|
|
282
|
+
if (typeof hideSnapGuides === 'function') hideSnapGuides();
|
|
283
|
+
return { bbox: newBbox, snapped: false, guides: [] };
|
|
284
|
+
}
|
|
285
|
+
const pxToMmX = figSize.width_mm / img.naturalWidth;
|
|
286
|
+
const pxToMmY = figSize.height_mm / img.naturalHeight;
|
|
287
|
+
const threshold = ANNOTATION_SNAP.threshold;
|
|
288
|
+
let snapped = false, snapX = newBbox.x, snapY = newBbox.y;
|
|
289
|
+
const guides = [];
|
|
290
|
+
// Dragged label's CENTER (in mm)
|
|
291
|
+
const cx = (newBbox.x + newBbox.width / 2) * pxToMmX;
|
|
292
|
+
const cy = (newBbox.y + newBbox.height / 2) * pxToMmY;
|
|
293
|
+
const hw = newBbox.width / 2, hh = newBbox.height / 2;
|
|
294
|
+
|
|
295
|
+
// Snap to CLOSEST panel label center (center-to-center alignment)
|
|
296
|
+
if (ANNOTATION_SNAP.snapToOtherLabels && annotationKey) {
|
|
297
|
+
const targets = getAnnotationSnapTargets(annotationKey);
|
|
298
|
+
// Find closest vertical target (X alignment)
|
|
299
|
+
let bestV = null, bestVDist = threshold;
|
|
300
|
+
for (const t of targets.v) {
|
|
301
|
+
const dist = Math.abs(cx - t.pos);
|
|
302
|
+
if (dist < bestVDist) { bestVDist = dist; bestV = t; }
|
|
303
|
+
}
|
|
304
|
+
if (bestV) {
|
|
305
|
+
snapX = bestV.pos / pxToMmX - hw;
|
|
306
|
+
snapped = true;
|
|
307
|
+
guides.push({type:'vertical',pos:bestV.pos,targetType:'label-center',strength:1});
|
|
308
|
+
console.log('[AnnotationSnap] X snap to', bestV.pos.toFixed(1), 'mm (dist:', bestVDist.toFixed(2), 'mm)');
|
|
309
|
+
}
|
|
310
|
+
// Find closest horizontal target (Y alignment)
|
|
311
|
+
let bestH = null, bestHDist = threshold;
|
|
312
|
+
for (const t of targets.h) {
|
|
313
|
+
const dist = Math.abs(cy - t.pos);
|
|
314
|
+
if (dist < bestHDist) { bestHDist = dist; bestH = t; }
|
|
315
|
+
}
|
|
316
|
+
if (bestH) {
|
|
317
|
+
snapY = bestH.pos / pxToMmY - hh;
|
|
318
|
+
snapped = true;
|
|
319
|
+
guides.push({type:'horizontal',pos:bestH.pos,targetType:'label-center',strength:1});
|
|
320
|
+
console.log('[AnnotationSnap] Y snap to', bestH.pos.toFixed(1), 'mm (dist:', bestHDist.toFixed(2), 'mm)');
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Show/hide snap guides
|
|
324
|
+
if (guides.length > 0 && typeof showSnapGuides === 'function' && rect) showSnapGuides(guides, rect);
|
|
325
|
+
else if (typeof hideSnapGuides === 'function') hideSnapGuides();
|
|
326
|
+
return { bbox: {...newBbox, x: snapX, y: snapY}, snapped: snapped, guides: guides };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Handle mouse up - complete the annotation drag
|
|
330
|
+
async function handleAnnotationDragEnd(event) {
|
|
331
|
+
console.log('[AnnotationDrag] handleAnnotationDragEnd called');
|
|
332
|
+
if (!isDraggingAnnotation) return;
|
|
333
|
+
|
|
334
|
+
// Remove temporary event listeners
|
|
335
|
+
document.removeEventListener('mousemove', handleAnnotationDragMove);
|
|
336
|
+
document.removeEventListener('mouseup', handleAnnotationDragEnd);
|
|
337
|
+
|
|
338
|
+
// Hide overlay, label, and snap guides
|
|
339
|
+
if (annotationDragOverlay) {
|
|
340
|
+
annotationDragOverlay.style.display = 'none';
|
|
341
|
+
annotationDragOverlay.style.borderColor = '#8b5cf6'; // Reset color
|
|
342
|
+
}
|
|
343
|
+
if (annotationDragLabel) {
|
|
344
|
+
annotationDragLabel.style.display = 'none';
|
|
345
|
+
}
|
|
346
|
+
if (typeof hideSnapGuides === 'function') hideSnapGuides();
|
|
347
|
+
document.body.style.cursor = '';
|
|
348
|
+
|
|
349
|
+
const img = document.getElementById('preview-image');
|
|
350
|
+
if (!img) {
|
|
351
|
+
isDraggingAnnotation = false;
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const rect = img.getBoundingClientRect();
|
|
356
|
+
|
|
357
|
+
// Calculate delta in pixels (for threshold check only)
|
|
358
|
+
const deltaX = event.clientX - annotationDragStartPos.x;
|
|
359
|
+
const deltaY = event.clientY - annotationDragStartPos.y;
|
|
360
|
+
|
|
361
|
+
// Only update if moved significantly (5px threshold)
|
|
362
|
+
if (Math.abs(deltaX) < 5 && Math.abs(deltaY) < 5) {
|
|
363
|
+
console.log('[AnnotationDrag] Movement below threshold, not updating');
|
|
364
|
+
isDraggingAnnotation = false;
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const axKey = Object.keys(panelPositions).sort()[annotationAxIndex];
|
|
369
|
+
const axPos = panelPositions[axKey];
|
|
370
|
+
if (!axPos || !figSize.width_mm || !figSize.height_mm) {
|
|
371
|
+
console.error('[AnnotationDrag] Cannot calculate axes-relative position');
|
|
372
|
+
isDraggingAnnotation = false;
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Calculate position using DELTA from original to snapped/current position
|
|
377
|
+
// This ensures coordinates stay valid even when snapping to labels from other axes
|
|
378
|
+
const pxToMmX = figSize.width_mm / img.naturalWidth;
|
|
379
|
+
const pxToMmY = figSize.height_mm / img.naturalHeight;
|
|
380
|
+
|
|
381
|
+
// Original bbox center in mm
|
|
382
|
+
const origCenterX_mm = (annotationDragStartBbox.x + annotationDragStartBbox.width / 2) * pxToMmX;
|
|
383
|
+
const origCenterY_mm = (annotationDragStartBbox.y + annotationDragStartBbox.height / 2) * pxToMmY;
|
|
384
|
+
|
|
385
|
+
let relX, relY;
|
|
386
|
+
|
|
387
|
+
if (annotationLastSnappedBbox) {
|
|
388
|
+
// Calculate delta from original to snapped position (in mm)
|
|
389
|
+
const snappedCenterX_mm = (annotationLastSnappedBbox.x + annotationLastSnappedBbox.width / 2) * pxToMmX;
|
|
390
|
+
const snappedCenterY_mm = (annotationLastSnappedBbox.y + annotationLastSnappedBbox.height / 2) * pxToMmY;
|
|
391
|
+
const deltaX_mm = snappedCenterX_mm - origCenterX_mm;
|
|
392
|
+
const deltaY_mm = snappedCenterY_mm - origCenterY_mm;
|
|
393
|
+
|
|
394
|
+
// Convert mm delta to axes-relative delta
|
|
395
|
+
const relDeltaX = deltaX_mm / axPos.width;
|
|
396
|
+
const relDeltaY = -deltaY_mm / axPos.height; // Y flipped (figure Y increases down, axes Y increases up)
|
|
397
|
+
|
|
398
|
+
// Apply delta to original axes-relative position
|
|
399
|
+
relX = annotationOriginalRelPos.x + relDeltaX;
|
|
400
|
+
relY = annotationOriginalRelPos.y + relDeltaY;
|
|
401
|
+
|
|
402
|
+
console.log('[AnnotationDrag] Using SNAPPED delta | Delta mm:', deltaX_mm.toFixed(2), deltaY_mm.toFixed(2),
|
|
403
|
+
'| Delta rel:', relDeltaX.toFixed(3), relDeltaY.toFixed(3), '| New pos:', relX.toFixed(3), relY.toFixed(3));
|
|
404
|
+
} else {
|
|
405
|
+
// Fallback: Calculate from cursor delta
|
|
406
|
+
const screenToImgScale = img.naturalWidth / rect.width;
|
|
407
|
+
const screenDeltaX = event.clientX - annotationDragStartPos.x;
|
|
408
|
+
const screenDeltaY = event.clientY - annotationDragStartPos.y;
|
|
409
|
+
const mmDeltaX = screenDeltaX * screenToImgScale * pxToMmX;
|
|
410
|
+
const mmDeltaY = screenDeltaY * screenToImgScale * pxToMmY;
|
|
411
|
+
const relDeltaX = mmDeltaX / axPos.width;
|
|
412
|
+
const relDeltaY = -mmDeltaY / axPos.height; // Y flipped
|
|
413
|
+
|
|
414
|
+
relX = annotationOriginalRelPos.x + relDeltaX;
|
|
415
|
+
relY = annotationOriginalRelPos.y + relDeltaY;
|
|
416
|
+
console.log('[AnnotationDrag] Using CURSOR delta | Delta:', relDeltaX.toFixed(3), relDeltaY.toFixed(3),
|
|
417
|
+
'| New pos:', relX.toFixed(3), relY.toFixed(3));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Get annotation type and index from key
|
|
421
|
+
const bboxInfo = annotationDragStartBbox;
|
|
422
|
+
const annotationType = bboxInfo.type || 'text';
|
|
423
|
+
const textIndex = bboxInfo.text_index !== undefined ? bboxInfo.text_index : 0;
|
|
424
|
+
|
|
425
|
+
// Apply the new position
|
|
426
|
+
await applyAnnotationPosition(annotationAxIndex, annotationType, textIndex, relX, relY);
|
|
427
|
+
|
|
428
|
+
// Update annotationPositions for undo support
|
|
429
|
+
if (annotationKey) {
|
|
430
|
+
annotationPositions[annotationKey] = {
|
|
431
|
+
x: relX,
|
|
432
|
+
y: relY,
|
|
433
|
+
type: annotationType
|
|
434
|
+
};
|
|
435
|
+
console.log('[AnnotationDrag] Updated position:', annotationKey, relX.toFixed(3), relY.toFixed(3));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Reset state
|
|
439
|
+
isDraggingAnnotation = false;
|
|
440
|
+
annotationDragStartPos = null;
|
|
441
|
+
annotationDragStartBbox = null;
|
|
442
|
+
annotationDragOffset = null;
|
|
443
|
+
annotationOriginalRelPos = null;
|
|
444
|
+
annotationLastSnappedBbox = null;
|
|
445
|
+
annotationKey = null;
|
|
446
|
+
console.log('[AnnotationDrag] Drag state reset');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Apply the dragged annotation position to the server
|
|
450
|
+
async function applyAnnotationPosition(axIndex, annotationType, textIndex, x, y) {
|
|
451
|
+
document.body.classList.add('loading');
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
const response = await fetch('/update_annotation_position', {
|
|
455
|
+
method: 'POST',
|
|
456
|
+
headers: { 'Content-Type': 'application/json' },
|
|
457
|
+
body: JSON.stringify({
|
|
458
|
+
ax_index: axIndex,
|
|
459
|
+
annotation_type: annotationType,
|
|
460
|
+
text_index: textIndex,
|
|
461
|
+
x: x,
|
|
462
|
+
y: y
|
|
463
|
+
})
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
const data = await response.json();
|
|
467
|
+
|
|
468
|
+
if (data.success) {
|
|
469
|
+
// Update preview image
|
|
470
|
+
const img = document.getElementById('preview-image');
|
|
471
|
+
if (img) {
|
|
472
|
+
await new Promise((resolve) => {
|
|
473
|
+
img.onload = resolve;
|
|
474
|
+
img.src = 'data:image/png;base64,' + data.image;
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Update bboxes and hitmap
|
|
479
|
+
if (data.bboxes) {
|
|
480
|
+
currentBboxes = data.bboxes;
|
|
481
|
+
loadHitmap();
|
|
482
|
+
updateHitRegions();
|
|
483
|
+
// Reinitialize annotation positions from new bboxes
|
|
484
|
+
initAnnotationPositions();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
console.log('[AnnotationDrag] Annotation position updated successfully');
|
|
488
|
+
} else {
|
|
489
|
+
console.error('[AnnotationDrag] Failed to update annotation:', data.error);
|
|
490
|
+
}
|
|
491
|
+
} catch (error) {
|
|
492
|
+
console.error('[AnnotationDrag] Failed to update annotation:', error);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
document.body.classList.remove('loading');
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Initialize on DOMContentLoaded
|
|
499
|
+
document.addEventListener('DOMContentLoaded', initAnnotationDrag);
|
|
500
|
+
"""
|
|
501
|
+
|
|
502
|
+
__all__ = ["SCRIPTS_ANNOTATION_DRAG"]
|
|
503
|
+
|
|
504
|
+
# EOF
|