@@ -238,14 +238,14 @@ export async function fetchUmapData() {
238238 scrollZoom : true ,
239239 } ;
240240
241- Plotly . newPlot ( "umapPlot" , [ allPointsTrace , currentImageTrace ] , layout , config ) . then ( ( gd ) => {
241+ Plotly . newPlot ( "umapPlot" , [ allPointsTrace , currentImageTrace ] , layout , config ) . then ( async ( gd ) => {
242242 document . getElementById ( "umapContent" ) . style . display = "block" ;
243243 setUmapWindowSize ( "fullscreen" ) ;
244244 hideUmapSpinner ( ) ;
245245
246246 window . dispatchEvent ( new CustomEvent ( "umapRedrawn" ) ) ;
247247
248- setUmapColorMode ( ) ;
248+ await setUmapColorMode ( ) ;
249249 let hoverTimer = null ;
250250 let isHovering = false ;
251251
@@ -410,7 +410,7 @@ export async function fetchUmapData() {
410410 // Dispatch event to notify that UMAP data has been loaded
411411 window . dispatchEvent ( new CustomEvent ( "umapDataLoaded" ) ) ;
412412
413- setUmapColorMode ( ) ;
413+ await setUmapColorMode ( ) ;
414414 } finally {
415415 hideUmapSpinner ( ) ;
416416 }
@@ -457,7 +457,7 @@ plotDiv.addEventListener("mouseleave", () => {
457457} ) ;
458458
459459// --- Dynamic Colorization ---
460- export function colorizeUmap ( { highlight = false , searchResults = [ ] } = { } ) {
460+ export async function colorizeUmap ( { highlight = false , searchResults = [ ] } = { } ) {
461461 if ( ! points . length ) {
462462 return ;
463463 }
@@ -467,6 +467,9 @@ export function colorizeUmap({ highlight = false, searchResults = [] } = {}) {
467467 return ;
468468 }
469469
470+ // Yield to the browser to allow spinner to render before heavy Plotly operations
471+ await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
472+
470473 if ( highlight && searchResults . length > 0 ) {
471474 const searchSet = new Set ( searchResults . map ( ( r ) => r . index ) ) ;
472475
@@ -475,7 +478,7 @@ export function colorizeUmap({ highlight = false, searchResults = [] } = {}) {
475478 const highlightedPoints = points . filter ( ( p ) => searchSet . has ( p . index ) ) ;
476479
477480 // Update main trace with only regular points
478- Plotly . restyle (
481+ await Plotly . restyle (
479482 "umapPlot" ,
480483 {
481484 x : [ regularPoints . map ( ( p ) => p . x ) ] ,
@@ -508,9 +511,9 @@ export function colorizeUmap({ highlight = false, searchResults = [] } = {}) {
508511 } ;
509512
510513 if ( highlightTraceIdx === - 1 ) {
511- Plotly . addTraces ( plotDiv , [ highlightTrace ] ) ;
514+ await Plotly . addTraces ( plotDiv , [ highlightTrace ] ) ;
512515 } else {
513- Plotly . restyle (
516+ await Plotly . restyle (
514517 plotDiv ,
515518 {
516519 x : [ highlightTrace . x ] ,
@@ -527,21 +530,21 @@ export function colorizeUmap({ highlight = false, searchResults = [] } = {}) {
527530 // Ensure Current Image marker stays on top
528531 const markerTraceIndex = plotDiv . data . findIndex ( ( trace ) => trace . name === "Current Image" ) ;
529532 if ( markerTraceIndex !== - 1 && markerTraceIndex !== plotDiv . data . length - 1 ) {
530- Plotly . moveTraces ( plotDiv , markerTraceIndex , plotDiv . data . length - 1 ) ;
533+ await Plotly . moveTraces ( plotDiv , markerTraceIndex , plotDiv . data . length - 1 ) ;
531534 }
532535 } else {
533536 // Remove highlight trace if it exists
534537 const highlightTraceIdx = plotDiv . data ?. findIndex ( ( t ) => t . name === "HighlightedPoints" ) ;
535538 if ( highlightTraceIdx !== - 1 ) {
536- Plotly . deleteTraces ( plotDiv , highlightTraceIdx ) ;
539+ await Plotly . deleteTraces ( plotDiv , highlightTraceIdx ) ;
537540 }
538541
539542 // Restore ALL points to main trace with normal coloring
540543 const markerColors = points . map ( ( p ) => getClusterColor ( p . cluster ) ) ;
541544 const markerAlphas = points . map ( ( p ) => ( p . cluster === - 1 ? 0.2 : 0.75 ) ) ;
542545 const markerSizes = points . map ( ( ) => 5 ) ;
543546
544- Plotly . restyle (
547+ await Plotly . restyle (
545548 "umapPlot" ,
546549 {
547550 x : [ points . map ( ( p ) => p . x ) ] ,
@@ -558,7 +561,7 @@ export function colorizeUmap({ highlight = false, searchResults = [] } = {}) {
558561 // Ensure Current Image marker stays on top after removing highlight
559562 const markerTraceIndex = plotDiv . data . findIndex ( ( trace ) => trace . name === "Current Image" ) ;
560563 if ( markerTraceIndex !== - 1 && markerTraceIndex !== plotDiv . data . length - 1 ) {
561- Plotly . moveTraces ( plotDiv , markerTraceIndex , plotDiv . data . length - 1 ) ;
564+ await Plotly . moveTraces ( plotDiv , markerTraceIndex , plotDiv . data . length - 1 ) ;
562565 }
563566 }
564567}
@@ -569,8 +572,8 @@ window.addEventListener("stateReady", () => {
569572 const highlightCheckbox = document . getElementById ( "umapHighlightSelection" ) ;
570573 if ( highlightCheckbox ) {
571574 highlightCheckbox . checked = false ;
572- highlightCheckbox . addEventListener ( "change" , ( ) => {
573- setUmapColorMode ( ) ;
575+ highlightCheckbox . addEventListener ( "change" , async ( ) => {
576+ await setUmapColorMode ( ) ;
574577 } ) ;
575578 }
576579
@@ -661,9 +664,11 @@ function updateExitFullscreenCheckboxState() {
661664}
662665
663666// --- Update colorization after search or cluster selection ---
664- window . addEventListener ( "searchResultsChanged" , ( e ) => {
667+ window . addEventListener ( "searchResultsChanged" , async ( e ) => {
665668 updateUmapColorModeAvailability ( e . detail . results ) ;
666- setUmapColorMode ( ) ;
669+ await setUmapColorMode ( ) ;
670+ // Hide spinner after colorization completes
671+ hideUmapSpinner ( ) ;
667672 // deactivate fullscreen mode when search results have come in (if enabled)
668673 if ( state . searchResults . length > 0 && isFullscreen && state . umapExitFullscreenOnSelection ) {
669674 setTimeout ( ( ) => toggleFullscreen ( false ) , 100 ) ; // slight delay to avoid flicker
@@ -920,8 +925,8 @@ function removeUmapThumbnail() {
920925 umapThumbnailDiv = null ;
921926}
922927
923- export function setUmapColorMode ( ) {
924- colorizeUmap ( {
928+ export async function setUmapColorMode ( ) {
929+ await colorizeUmap ( {
925930 highlight : document . getElementById ( "umapHighlightSelection" ) ?. checked ,
926931 searchResults : state . searchResults ,
927932 } ) ;
@@ -943,7 +948,7 @@ function updateUmapColorModeAvailability(searchResults = []) {
943948 highlightCheckbox . disabled = true ;
944949 highlightCheckbox . parentElement . style . opacity = "0.5" ;
945950 }
946- setUmapColorMode ( ) ;
951+ // Note: setUmapColorMode is called by the searchResultsChanged event handler
947952}
948953
949954// ------------- Handling Landmark Thumbnails -------------
@@ -1265,29 +1270,25 @@ async function handleClusterClick(clickedIndex) {
12651270 // Yield to the browser to allow spinner to render before heavy computation
12661271 await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
12671272
1268- try {
1269- const clickedCluster = clickedPoint . cluster ;
1270- const clusterColor = getClusterColor ( clickedCluster ) ;
1271- let clusterIndices = points . filter ( ( p ) => p . cluster === clickedCluster ) . map ( ( p ) => p . index ) ;
1273+ const clickedCluster = clickedPoint . cluster ;
1274+ const clusterColor = getClusterColor ( clickedCluster ) ;
1275+ let clusterIndices = points . filter ( ( p ) => p . cluster === clickedCluster ) . map ( ( p ) => p . index ) ;
12721276
1273- // Remove clickedFilename from the list
1274- clusterIndices = clusterIndices . filter ( ( fn ) => fn !== clickedIndex ) ;
1277+ // Remove clickedFilename from the list
1278+ clusterIndices = clusterIndices . filter ( ( fn ) => fn !== clickedIndex ) ;
12751279
1276- // --- Greedy random walk order from clicked point ---
1277- const sort_algorithm = clusterIndices . length > randomWalkMaxSize ? proximityClusterOrder : randomWalkClusterOrder ;
1278- const sortedClusterIndices = sort_algorithm ( [ clickedIndex , ...clusterIndices ] , points , clickedIndex ) ;
1280+ // --- Greedy random walk order from clicked point ---
1281+ const sort_algorithm = clusterIndices . length > randomWalkMaxSize ? proximityClusterOrder : randomWalkClusterOrder ;
1282+ const sortedClusterIndices = sort_algorithm ( [ clickedIndex , ...clusterIndices ] , points , clickedIndex ) ;
12791283
1280- const clusterMembers = sortedClusterIndices . map ( ( index ) => ( {
1281- index : index ,
1282- cluster : clickedCluster === - 1 ? "unclustered" : clickedCluster ,
1283- color : clusterColor ,
1284- } ) ) ;
1284+ const clusterMembers = sortedClusterIndices . map ( ( index ) => ( {
1285+ index : index ,
1286+ cluster : clickedCluster === - 1 ? "unclustered" : clickedCluster ,
1287+ color : clusterColor ,
1288+ } ) ) ;
12851289
1286- setSearchResults ( clusterMembers , "cluster" ) ;
1287- } finally {
1288- // Always hide spinner, even if there's an error
1289- hideUmapSpinner ( ) ;
1290- }
1290+ setSearchResults ( clusterMembers , "cluster" ) ;
1291+ // Note: spinner is hidden by searchResultsChanged event handler after colorization completes
12911292}
12921293
12931294// Handle single image selection (navigate to clicked image)
@@ -1303,21 +1304,17 @@ async function handleImageClick(clickedIndex) {
13031304 // Yield to the browser to allow spinner to render before heavy computation
13041305 await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
13051306
1306- try {
1307- // Clear any existing search selection
1308- exitSearchMode ( ) ;
1307+ // Clear any existing search selection
1308+ exitSearchMode ( ) ;
13091309
1310- // Navigate directly to the clicked image without entering search mode
1311- slideState . navigateToIndex ( clickedIndex , false ) ;
1310+ // Navigate directly to the clicked image without entering search mode
1311+ slideState . navigateToIndex ( clickedIndex , false ) ;
13121312
1313- // Exit fullscreen mode if enabled
1314- if ( isFullscreen && state . umapExitFullscreenOnSelection ) {
1315- setTimeout ( ( ) => toggleFullscreen ( false ) , 100 ) ; // slight delay to avoid flicker
1316- }
1317- } finally {
1318- // Always hide spinner, even if there's an error
1319- hideUmapSpinner ( ) ;
1313+ // Exit fullscreen mode if enabled
1314+ if ( isFullscreen && state . umapExitFullscreenOnSelection ) {
1315+ setTimeout ( ( ) => toggleFullscreen ( false ) , 100 ) ; // slight delay to avoid flicker
13201316 }
1317+ // Note: spinner is hidden by searchResultsChanged event handler after colorization completes
13211318}
13221319
13231320// -------------------- Window Management --------------------
0 commit comments