figrecipe 0.7.4__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 +74 -76
- figrecipe/__main__.py +12 -0
- figrecipe/_api/_panel.py +67 -0
- figrecipe/_api/_save.py +100 -4
- 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 +2 -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/_editor/__init__.py +36 -36
- figrecipe/_editor/_bbox/_extract.py +155 -9
- figrecipe/_editor/_bbox/_extract_text.py +124 -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 +157 -16
- figrecipe/_editor/_helpers.py +17 -8
- figrecipe/_editor/_hitmap/_detect.py +89 -32
- figrecipe/_editor/_hitmap_main.py +4 -4
- figrecipe/_editor/_overrides.py +4 -1
- figrecipe/_editor/_plot_types_registry.py +190 -0
- figrecipe/_editor/_render_overrides.py +38 -11
- figrecipe/_editor/_renderer.py +46 -1
- figrecipe/_editor/_routes_annotation.py +114 -0
- figrecipe/_editor/_routes_axis.py +35 -6
- figrecipe/_editor/_routes_captions.py +130 -0
- figrecipe/_editor/_routes_composition.py +270 -0
- figrecipe/_editor/_routes_core.py +15 -173
- figrecipe/_editor/_routes_datatable.py +364 -0
- figrecipe/_editor/_routes_element.py +37 -19
- 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 +28 -8
- figrecipe/_editor/_templates/__init__.py +40 -2
- figrecipe/_editor/_templates/_html.py +97 -103
- 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 +58 -0
- figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
- figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
- figrecipe/_editor/_templates/_scripts/_api.py +1 -1
- figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
- figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
- figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
- figrecipe/_editor/_templates/_scripts/_core.py +94 -37
- 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/_element_editor.py +17 -2
- figrecipe/_editor/_templates/_scripts/_files.py +274 -40
- figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
- figrecipe/_editor/_templates/_scripts/_hitmap.py +87 -84
- figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
- figrecipe/_editor/_templates/_scripts/_legend_drag.py +5 -0
- figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag.py +219 -48
- figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
- figrecipe/_editor/_templates/_scripts/_panel_position.py +238 -54
- 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 +8 -1
- figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
- figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
- figrecipe/_editor/_templates/_scripts/_zoom.py +52 -19
- figrecipe/_editor/_templates/_styles/__init__.py +9 -0
- figrecipe/_editor/_templates/_styles/_base.py +47 -0
- figrecipe/_editor/_templates/_styles/_buttons.py +127 -6
- figrecipe/_editor/_templates/_styles/_composition.py +87 -0
- figrecipe/_editor/_templates/_styles/_controls.py +168 -3
- 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 +5 -5
- figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
- figrecipe/_editor/_templates/_styles/_forms.py +98 -0
- figrecipe/_editor/_templates/_styles/_hitmap.py +7 -0
- figrecipe/_editor/_templates/_styles/_modals.py +29 -0
- figrecipe/_editor/_templates/_styles/_overlays.py +5 -5
- figrecipe/_editor/_templates/_styles/_preview.py +213 -8
- 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 +2 -0
- figrecipe/_recorder.py +28 -3
- figrecipe/_reproducer/_core.py +60 -49
- figrecipe/_utils/__init__.py +3 -0
- figrecipe/_utils/_bundle.py +205 -0
- figrecipe/_wrappers/_axes.py +150 -2
- figrecipe/_wrappers/_caption_generator.py +218 -0
- figrecipe/_wrappers/_figure.py +26 -1
- figrecipe/_wrappers/_stat_annotation.py +274 -0
- figrecipe/styles/_style_applier.py +10 -2
- figrecipe/styles/presets/SCITEX.yaml +11 -4
- {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/METADATA +144 -146
- figrecipe-0.9.0.dist-info/RECORD +277 -0
- figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
- figrecipe-0.7.4.dist-info/RECORD +0 -188
- {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
- {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -45,6 +45,11 @@ async function loadHitmap() {
|
|
|
45
45
|
if (overlay) {
|
|
46
46
|
overlay.src = hitmapImg.src;
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
// Sync datatable tab colors now that colorMap is loaded
|
|
50
|
+
if (typeof updateTabColors === 'function') {
|
|
51
|
+
updateTabColors();
|
|
52
|
+
}
|
|
48
53
|
};
|
|
49
54
|
hitmapImg.src = 'data:image/png;base64,' + data.image;
|
|
50
55
|
} catch (error) {
|
|
@@ -80,16 +85,15 @@ function drawHitRegions() {
|
|
|
80
85
|
const overlay = document.getElementById('hitregion-overlay');
|
|
81
86
|
overlay.innerHTML = '';
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
console.log('Image not loaded yet, deferring hit regions draw');
|
|
88
|
-
return;
|
|
88
|
+
// Context menu on overlay (pointer-events: auto captures right-clicks)
|
|
89
|
+
if (!overlay._ctxInit) {
|
|
90
|
+
overlay.addEventListener('contextmenu', (e) => { if (typeof showCanvasContextMenu === 'function') showCanvasContextMenu(e); });
|
|
91
|
+
overlay._ctxInit = true;
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
const img = document.getElementById('preview-image');
|
|
95
|
+
if (!img.naturalWidth || !img.naturalHeight) { console.log('Image not loaded yet'); return; }
|
|
96
|
+
|
|
93
97
|
overlay.setAttribute('viewBox', `0 0 ${img.naturalWidth} ${img.naturalHeight}`);
|
|
94
98
|
overlay.style.width = `${img.naturalWidth}px`;
|
|
95
99
|
overlay.style.height = `${img.naturalHeight}px`;
|
|
@@ -102,13 +106,25 @@ function drawHitRegions() {
|
|
|
102
106
|
|
|
103
107
|
console.log('Drawing hit regions:', Object.keys(currentBboxes).length, 'elements');
|
|
104
108
|
|
|
105
|
-
//
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
// Draw panel hit regions FIRST (lowest z-order) to catch empty space clicks
|
|
110
|
+
const panelBboxes = currentBboxes?._meta?.panel_bboxes;
|
|
111
|
+
if (panelBboxes) { for (const [axIdx, pb] of Object.entries(panelBboxes)) {
|
|
112
|
+
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
113
|
+
rect.setAttribute('x', pb.x); rect.setAttribute('y', pb.y);
|
|
114
|
+
rect.setAttribute('width', pb.width); rect.setAttribute('height', pb.height);
|
|
115
|
+
rect.setAttribute('class', 'hitregion-rect panel-region'); rect.setAttribute('data-key', `ax${axIdx}_axes`);
|
|
116
|
+
rect.addEventListener('click', (e) => { e.stopPropagation();
|
|
117
|
+
const el = { key: `ax${axIdx}_axes`, type: 'panel', label: `Panel ${axIdx}`, ax_index: parseInt(axIdx), ...pb };
|
|
118
|
+
if (e.ctrlKey || e.metaKey) { if (typeof toggleInSelection === 'function') toggleInSelection(el); }
|
|
119
|
+
else { if (typeof clearMultiSelection === 'function') clearMultiSelection(); selectElement(el); }
|
|
120
|
+
});
|
|
121
|
+
rect.addEventListener('mousedown', (e) => { if (e.button === 0 && !e.ctrlKey && !e.metaKey && !e.altKey && typeof handlePanelDragStart === 'function') handlePanelDragStart(e); });
|
|
122
|
+
overlay.appendChild(rect);
|
|
123
|
+
}}
|
|
124
|
+
// Drawing z-order: axes lowest (background), panel_label/text highest (foreground)
|
|
125
|
+
const zOrderPriority = { 'axes': 0, 'fill': 1, 'spine': 2, 'image': 3, 'contour': 3, 'bar': 4, 'pie': 4,
|
|
126
|
+
'quiver': 4, 'line': 5, 'scatter': 6, 'xticks': 7, 'yticks': 7, 'title': 8, 'xlabel': 8, 'ylabel': 8, 'legend': 9, 'panel_label': 10, 'text': 10 };
|
|
127
|
+
// Convert to array, filter, and sort by z-order (axes lowest, panel_label highest)
|
|
112
128
|
const sortedEntries = Object.entries(currentBboxes)
|
|
113
129
|
.filter(([key, bbox]) => key !== '_meta' && bbox && typeof bbox.x !== 'undefined')
|
|
114
130
|
.sort((a, b) => (zOrderPriority[a[1].type] || 5) - (zOrderPriority[b[1].type] || 5));
|
|
@@ -151,10 +167,11 @@ function drawHitRegions() {
|
|
|
151
167
|
shape.addEventListener('mouseleave', () => handleHitRegionLeave());
|
|
152
168
|
shape.addEventListener('click', (e) => handleHitRegionClick(e, key, enrichedBbox));
|
|
153
169
|
|
|
154
|
-
// Add mousedown for drag (legend or panel)
|
|
170
|
+
// Add mousedown for drag (legend, annotation, or panel)
|
|
155
171
|
shape.addEventListener('mousedown', (e) => {
|
|
156
172
|
if (e.button !== 0 || e.ctrlKey || e.metaKey || e.altKey) return;
|
|
157
173
|
if (bbox.type === 'legend' && typeof startLegendDrag === 'function') { startLegendDrag(e, key); return; }
|
|
174
|
+
if ((bbox.type === 'panel_label' || bbox.type === 'text') && typeof startAnnotationDrag === 'function') { startAnnotationDrag(e, key); return; }
|
|
158
175
|
if (typeof handlePanelDragStart === 'function') handlePanelDragStart(e);
|
|
159
176
|
});
|
|
160
177
|
|
|
@@ -201,7 +218,7 @@ function _createScatterShape(bbox, key, originalColor, offsetX, offsetY, scaleX,
|
|
|
201
218
|
shape.style.setProperty('--element-color', originalColor);
|
|
202
219
|
}
|
|
203
220
|
|
|
204
|
-
const hitRadius =
|
|
221
|
+
const hitRadius = 8; // Larger radius for easier click targeting
|
|
205
222
|
const allCircles = [];
|
|
206
223
|
|
|
207
224
|
bbox.points.forEach((pt, idx) => {
|
|
@@ -239,7 +256,9 @@ function _createScatterShape(bbox, key, originalColor, offsetX, offsetY, scaleX,
|
|
|
239
256
|
// Helper: Create rectangle shape for other elements
|
|
240
257
|
function _createRectShape(bbox, key, originalColor, offsetX, offsetY, scaleX, scaleY) {
|
|
241
258
|
let regionClass = 'hitregion-rect';
|
|
242
|
-
if (bbox.type === '
|
|
259
|
+
if (bbox.type === 'axes') {
|
|
260
|
+
regionClass += ' axes-region'; // Special class for axes - lower z-order
|
|
261
|
+
} else if (bbox.type === 'line' || bbox.type === 'scatter') {
|
|
243
262
|
regionClass += ' line-region';
|
|
244
263
|
} else if (['title', 'xlabel', 'ylabel', 'suptitle', 'supxlabel', 'supylabel'].includes(bbox.type)) {
|
|
245
264
|
regionClass += ' text-region';
|
|
@@ -282,22 +301,13 @@ function handleHitRegionHover(key, bbox) {
|
|
|
282
301
|
}
|
|
283
302
|
}
|
|
284
303
|
|
|
285
|
-
// Highlight all elements in a group
|
|
286
304
|
function highlightGroupElements(keys) {
|
|
287
|
-
keys.forEach(key => {
|
|
288
|
-
const hitRegion = document.querySelector(`[data-key="${key}"]`);
|
|
289
|
-
if (hitRegion) {
|
|
290
|
-
hitRegion.classList.add('group-hovered');
|
|
291
|
-
}
|
|
292
|
-
});
|
|
305
|
+
keys.forEach(key => { const el = document.querySelector(`[data-key="${key}"]`); if (el) el.classList.add('group-hovered'); });
|
|
293
306
|
}
|
|
294
307
|
|
|
295
|
-
// Handle leaving hit region
|
|
296
308
|
function handleHitRegionLeave() {
|
|
297
309
|
hoveredElement = null;
|
|
298
|
-
document.querySelectorAll('.group-hovered').forEach(el =>
|
|
299
|
-
el.classList.remove('group-hovered');
|
|
300
|
-
});
|
|
310
|
+
document.querySelectorAll('.group-hovered').forEach(el => el.classList.remove('group-hovered'));
|
|
301
311
|
}
|
|
302
312
|
|
|
303
313
|
// Handle click on hit region with Alt+Click cycling support
|
|
@@ -311,33 +321,30 @@ function handleHitRegionClick(event, key, bbox) {
|
|
|
311
321
|
const colorMapInfo = (colorMap && colorMap[key]) || {};
|
|
312
322
|
const element = { key, ...bbox, ...colorMapInfo };
|
|
313
323
|
|
|
314
|
-
if (event.
|
|
324
|
+
if (event.ctrlKey || event.metaKey) {
|
|
325
|
+
// Ctrl+Click: toggle multi-selection
|
|
326
|
+
if (typeof toggleInSelection === 'function') {
|
|
327
|
+
toggleInSelection(element);
|
|
328
|
+
if (typeof drawMultiSelection === 'function') drawMultiSelection();
|
|
329
|
+
} else { selectElement(element); }
|
|
330
|
+
} else if (event.altKey) {
|
|
315
331
|
// Alt+Click: cycle through overlapping elements
|
|
316
332
|
const clickPos = { x: event.clientX, y: event.clientY };
|
|
317
|
-
const samePosition = lastClickPosition &&
|
|
318
|
-
Math.abs(lastClickPosition.x - clickPos.x) < 5 &&
|
|
319
|
-
Math.abs(lastClickPosition.y - clickPos.y) < 5;
|
|
320
|
-
|
|
333
|
+
const samePosition = lastClickPosition && Math.abs(lastClickPosition.x - clickPos.x) < 5 && Math.abs(lastClickPosition.y - clickPos.y) < 5;
|
|
321
334
|
if (samePosition && overlappingElements.length > 1) {
|
|
322
335
|
cycleIndex = (cycleIndex + 1) % overlappingElements.length;
|
|
323
336
|
selectElement(overlappingElements[cycleIndex]);
|
|
324
337
|
} else {
|
|
325
338
|
overlappingElements = findOverlappingElements(clickPos);
|
|
326
|
-
cycleIndex = 0;
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (overlappingElements.length > 0) {
|
|
330
|
-
selectElement(overlappingElements[0]);
|
|
331
|
-
} else {
|
|
332
|
-
selectElement(element);
|
|
333
|
-
}
|
|
339
|
+
cycleIndex = 0; lastClickPosition = clickPos;
|
|
340
|
+
selectElement(overlappingElements.length > 0 ? overlappingElements[0] : element);
|
|
334
341
|
}
|
|
335
342
|
} else {
|
|
336
|
-
// Normal click:
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
cycleIndex = 0;
|
|
343
|
+
// Normal click: clear multi-selection, use priority-based selection
|
|
344
|
+
if (typeof clearMultiSelection === 'function') clearMultiSelection();
|
|
345
|
+
const overlapping = findOverlappingElements({ x: event.clientX, y: event.clientY });
|
|
346
|
+
selectElement(overlapping.length > 0 ? overlapping[0] : element);
|
|
347
|
+
lastClickPosition = null; overlappingElements = []; cycleIndex = 0;
|
|
341
348
|
}
|
|
342
349
|
}
|
|
343
350
|
|
|
@@ -345,40 +352,46 @@ function handleHitRegionClick(event, key, bbox) {
|
|
|
345
352
|
function findOverlappingElements(screenPos) {
|
|
346
353
|
const img = document.getElementById('preview-image');
|
|
347
354
|
const imgRect = img.getBoundingClientRect();
|
|
348
|
-
|
|
349
355
|
const imgX = (screenPos.x - imgRect.left) * (img.naturalWidth / imgRect.width);
|
|
350
356
|
const imgY = (screenPos.y - imgRect.top) * (img.naturalHeight / imgRect.height);
|
|
351
|
-
|
|
352
357
|
const overlapping = [];
|
|
353
358
|
|
|
354
359
|
for (const [key, bbox] of Object.entries(currentBboxes)) {
|
|
355
360
|
if (key === '_meta') continue;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
imgY >= bbox.y && imgY <= bbox.y + bbox.height) {
|
|
359
|
-
overlapping.push({ key, ...bbox });
|
|
361
|
+
if (imgX >= bbox.x && imgX <= bbox.x + bbox.width && imgY >= bbox.y && imgY <= bbox.y + bbox.height) {
|
|
362
|
+
overlapping.push({ key, ...bbox, ...(colorMap?.[key] || {}) });
|
|
360
363
|
}
|
|
361
|
-
|
|
362
364
|
// For lines with points, check proximity
|
|
363
|
-
if (bbox.points
|
|
365
|
+
if (bbox.points?.length > 1) {
|
|
364
366
|
for (const pt of bbox.points) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (!overlapping.find(e => e.key === key)) {
|
|
368
|
-
overlapping.push({ key, ...bbox });
|
|
369
|
-
}
|
|
370
|
-
break;
|
|
367
|
+
if (Math.hypot(imgX - pt[0], imgY - pt[1]) < 15 && !overlapping.find(e => e.key === key)) {
|
|
368
|
+
overlapping.push({ key, ...bbox, ...(colorMap?.[key] || {}) }); break;
|
|
371
369
|
}
|
|
372
370
|
}
|
|
373
371
|
}
|
|
374
372
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
373
|
+
// Panel bboxes as fallback - catches empty space within panels
|
|
374
|
+
const panelBboxes = currentBboxes?._meta?.panel_bboxes;
|
|
375
|
+
if (panelBboxes) {
|
|
376
|
+
for (const [axIdx, pb] of Object.entries(panelBboxes)) {
|
|
377
|
+
if (imgX >= pb.x && imgX <= pb.x + pb.width && imgY >= pb.y && imgY <= pb.y + pb.height) {
|
|
378
|
+
const axKey = `ax${axIdx}_axes`;
|
|
379
|
+
if (!overlapping.find(e => e.key === axKey)) {
|
|
380
|
+
overlapping.push({ key: axKey, type: 'panel', label: `Panel ${axIdx}`, ax_index: parseInt(axIdx), ...pb });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// Click priority (lower = higher priority). 'panel' lowest - selected only in empty space
|
|
386
|
+
const clickPriority = { 'scatter': 0, 'legend': 1, 'panel_label': 2, 'text': 2, 'title': 3, 'xlabel': 3, 'ylabel': 3,
|
|
387
|
+
'line': 4, 'bar': 5, 'pie': 5, 'hist': 5, 'contour': 6, 'quiver': 6, 'image': 6, 'fill': 7,
|
|
388
|
+
'xticks': 8, 'yticks': 8, 'spine': 9, 'axes': 10, 'panel': 11 };
|
|
389
|
+
overlapping.forEach(e => {
|
|
390
|
+
e._d = Infinity; const bb = currentBboxes[e.key];
|
|
391
|
+
if (bb?.points?.length) { for (const p of bb.points) { const d = Math.hypot(imgX - p[0], imgY - p[1]); if (d < e._d) e._d = d; } }
|
|
392
|
+
else { e._d = Math.hypot(imgX - (e.x + e.width/2), imgY - (e.y + e.height/2)); }
|
|
393
|
+
});
|
|
394
|
+
overlapping.sort((a, b) => { const p = (clickPriority[a.type] ?? 6) - (clickPriority[b.type] ?? 6); return p !== 0 ? p : a._d - b._d; });
|
|
382
395
|
return overlapping;
|
|
383
396
|
}
|
|
384
397
|
|
|
@@ -469,37 +482,27 @@ function findGroupElements(callId) {
|
|
|
469
482
|
// Get representative color for a call_id group
|
|
470
483
|
function getGroupRepresentativeColor(callId, fallbackColor) {
|
|
471
484
|
if (!callId || !colorMap) return fallbackColor;
|
|
472
|
-
|
|
473
485
|
const groupElements = findGroupElements(callId);
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const firstColor = groupElements[0].original_color;
|
|
477
|
-
if (!firstColor) return fallbackColor;
|
|
478
|
-
|
|
479
|
-
const allSameColor = groupElements.every(el => el.original_color === firstColor);
|
|
480
|
-
return allSameColor ? firstColor : firstColor;
|
|
486
|
+
return groupElements.length > 0 && groupElements[0].original_color ? groupElements[0].original_color : fallbackColor;
|
|
481
487
|
}
|
|
482
488
|
|
|
483
489
|
// Select an element (and its logical group if applicable)
|
|
484
490
|
function selectElement(element) {
|
|
485
491
|
selectedElement = element;
|
|
486
|
-
|
|
487
492
|
const callId = element.call_id || element.label;
|
|
488
493
|
const groupElements = findGroupElements(callId);
|
|
489
|
-
|
|
490
494
|
selectedElement.groupElements = groupElements.length > 1 ? groupElements : null;
|
|
491
495
|
|
|
492
496
|
drawSelection(element.key);
|
|
493
497
|
autoSwitchTab(element.type);
|
|
494
498
|
updateTabHints();
|
|
495
499
|
syncPropertiesToElement(element);
|
|
500
|
+
if (element && typeof syncDatatableToElement === 'function') syncDatatableToElement(element);
|
|
496
501
|
|
|
497
|
-
//
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
selectPanelByIndex(axIndex);
|
|
502
|
-
}
|
|
502
|
+
// Always sync panel position for any element that belongs to a panel
|
|
503
|
+
const axIndex = element.ax_index !== undefined ? element.ax_index : getPanelIndexFromKey(element.key);
|
|
504
|
+
if (axIndex !== null && typeof selectPanelByIndex === 'function') {
|
|
505
|
+
selectPanelByIndex(axIndex, element.type === 'axes');
|
|
503
506
|
}
|
|
504
507
|
}
|
|
505
508
|
"""
|