@@ -137,6 +137,7 @@ <h4>Search</h4>
137137
138138 const baseNodeRadius = 5 ;
139139 const clickedNodeRadius = 7 ;
140+ const degreeScaleFactor = 0.9 ; // How much to scale nodes based on degree
140141
141142 async function renderd3 ( ) {
142143 // Current clicked node.
@@ -219,10 +220,21 @@ <h4>Search</h4>
219220 backwardChildren . get ( link . target . id ) . add ( link . source . id ) ;
220221 } ) ;
221222
222- // n => baseNodeRadius + forwardChildren.get(n.id).size/7.0;
223- // ^for size dependent on number of bits, but I decided not to implement it on this iteration of the graph.
223+ // Calculate node radius based on degree (total connections)
224+ function getNodeRadius ( nodeId , isClicked = false ) {
225+ const forwardDegree = forwardChildren . get ( nodeId ) . size ;
226+ const backwardDegree = backwardChildren . get ( nodeId ) . size ;
227+ const totalDegree = forwardDegree + backwardDegree ;
228+
229+ // Scale radius based on square root of degree for better visual balance
230+ const scaledRadius = baseNodeRadius + Math . sqrt ( totalDegree ) * degreeScaleFactor ;
231+
232+ // If clicked, enlarge further
233+ return isClicked ? scaledRadius + 2 : scaledRadius ;
234+ }
235+
224236 node . append ( "circle" )
225- . attr ( "r" , baseNodeRadius )
237+ . attr ( "r" , n => getNodeRadius ( n . id ) )
226238 . attr ( "fill" , n => ( forwardChildren . get ( n . id ) . size != 0 ) ? "#4E76AF" : "#679BE5" )
227239 . attr ( "stroke" , "#EEE" )
228240 . attr ( "stroke-width" , 1.5 ) ;
@@ -237,7 +249,7 @@ <h4>Search</h4>
237249 . attr ( "z-index" , "1001" )
238250 . attr ( "font-size" , maxTextSize )
239251 . attr ( "dx" , 0 ) // Horizontal centering
240- . attr ( "dy" , - baseNodeRadius ) // Position label slightly above the node
252+ . attr ( "dy" , d => - getNodeRadius ( d . id ) - 2 ) // Position label above the node based on its size
241253 . attr ( "text-anchor" , "middle" ) // Anchor text in the middle for centering
242254 . text ( d => d . name . split ( "(" ) [ 0 ] )
243255 . style ( "display" , "none" ) ; // Initially hide all labels
@@ -336,7 +348,7 @@ <h4>Search</h4>
336348 // Reset nodes to the unselected state
337349 function resetNodes ( ) {
338350 activeNode = null ;
339- node . selectAll ( "circle" ) . transition ( ) . duration ( 300 ) . style ( "opacity" , 1 ) . attr ( "r" , baseNodeRadius ) ;
351+ node . selectAll ( "circle" ) . transition ( ) . duration ( 300 ) . style ( "opacity" , 1 ) . attr ( "r" , n => getNodeRadius ( n . id ) ) ;
340352 node . selectAll ( "text" ) . transition ( ) . duration ( 300 ) . style ( "opacity" , 1 ) ;
341353 link . transition ( ) . duration ( 300 ) . style ( "stroke-opacity" , 0.6 ) ;
342354 link . attr ( "stroke" , "#999" ) ;
@@ -371,7 +383,7 @@ <h4>Search</h4>
371383 node . selectAll ( "circle" )
372384 . transition ( ) . duration ( 300 )
373385 . style ( "opacity" , otherNode => highlightableNodeIds . has ( otherNode . id ) || otherNode . id === d . id ? 1 : 0.1 )
374- . attr ( "r" , otherNode => otherNode . id === d . id ? clickedNodeRadius : baseNodeRadius ) ; // Enlarge clicked node
386+ . attr ( "r" , otherNode => getNodeRadius ( otherNode . id , otherNode . id === d . id ) ) ; // Enlarge clicked node
375387
376388 node . selectAll ( "text" )
377389 . transition ( ) . duration ( 300 )
@@ -463,10 +475,9 @@ <h4>Search</h4>
463475 const sourceNode = d_link . source ;
464476 const targetNode = d_link . target ;
465477
466- let currentTargetRadius = baseNodeRadius ;
467- if ( activeNode && targetNode . id === activeNode . id ) {
468- currentTargetRadius = clickedNodeRadius ;
469- }
478+ // Use scaled radius based on degree
479+ const isTargetClicked = activeNode && targetNode . id === activeNode . id ;
480+ const currentTargetRadius = getNodeRadius ( targetNode . id , isTargetClicked ) ;
470481
471482 const dx = targetNode . x - sourceNode . x ;
472483 const dy = targetNode . y - sourceNode . y ;
@@ -568,19 +579,37 @@ <h4>Search</h4>
568579 searchFeedback . textContent = '' ;
569580 if ( ! searchTerm ) return ;
570581
571- const foundNode = nodes . find ( n => n . id . toLowerCase ( ) . includes ( searchTerm )
572- || n . name . toLowerCase ( ) . includes ( searchTerm ) ) ;
582+ // Find ALL matching nodes, not just the first one
583+ const matchingNodes = nodes . filter ( n => n . id . toLowerCase ( ) . includes ( searchTerm )
584+ || n . name . toLowerCase ( ) . includes ( searchTerm ) ) ;
585+
586+ if ( matchingNodes . length > 0 ) {
587+ const foundNode = matchingNodes [ 0 ] ;
588+
589+ // Keep search highlights visible (don't clear them)
590+ // This shows all matches with gold stroke
591+
592+ // Show feedback about multiple matches
593+ if ( matchingNodes . length > 1 ) {
594+ const matchNames = matchingNodes . slice ( 0 , 5 ) . map ( n => n . name ) . join ( ', ' ) ;
595+ const remaining = matchingNodes . length > 5 ? ` (+${ matchingNodes . length - 5 } more)` : '' ;
596+ searchFeedback . textContent = `Found ${ matchingNodes . length } matches: ${ matchNames } ${ remaining } ` ;
597+ searchFeedback . style . color = '#5bc0de' ; // Info color (blue)
598+ } else {
599+ searchFeedback . textContent = `Found: ${ foundNode . name } ` ;
600+ searchFeedback . style . color = '#5cb85c' ; // Success color (green)
601+ }
573602
574- if ( foundNode ) {
575- clearSearchHighlights ( ) ;
603+ // Click and zoom to the first match
576604 nodeClicked ( { stopPropagation : ( ) => { } } , foundNode , false ) ;
577605 const scale = d3 . zoomTransform ( svg . node ( ) ) . k ;
578606 const x = - foundNode . x * scale ;
579607 const y = - foundNode . y * scale ;
580608 const transform = d3 . zoomIdentity . translate ( x , y ) . scale ( scale ) ;
581609 svg . transition ( ) . duration ( 750 ) . call ( zoom . transform , transform ) ;
582610 } else {
583- searchFeedback . textContent = `Node starting with "${ searchTerm } " not found.` ;
611+ searchFeedback . textContent = `No matches found for "${ searchTerm } "` ;
612+ searchFeedback . style . color = '#d9534f' ; // Error color (red)
584613 }
585614 }
586615
0 commit comments