@@ -42,8 +42,8 @@ export function SynapseGraph() {
4242 canvas,
4343 resizeTo : canvas . parentElement || undefined ,
4444 backgroundColor : 0x050510 ,
45- antialias : true ,
46- resolution : window . devicePixelRatio || 1 ,
45+ antialias : false ,
46+ resolution : Math . min ( window . devicePixelRatio || 1 , 2 ) ,
4747 autoDensity : true ,
4848 } ) ;
4949 appRef . current = app ;
@@ -61,100 +61,100 @@ export function SynapseGraph() {
6161 return ;
6262 }
6363
64- const edgesContainer = new Container ( ) ;
65- const nodesContainer = new Container ( ) ;
66- app . stage . addChild ( edgesContainer ) ;
67- app . stage . addChild ( nodesContainer ) ;
68-
69- // Run d3-force
64+ // Build force nodes
7065 const forceNodes : ForceNode [ ] = nodes . map ( n => ( {
7166 id : n . id ,
7267 layer : n . layer ,
7368 strength : n . strength ,
7469 label : n . label || '' ,
7570 } ) ) ;
7671
77- const forceEdges = edges . map ( e => ( {
72+ const forceEdges : SimulationLinkDatum < ForceNode > [ ] = edges . map ( e => ( {
7873 source : e . source ,
7974 target : e . target ,
8075 type : e . type ,
81- } ) ) ;
76+ } ) ) as SimulationLinkDatum < ForceNode > [ ] ;
8277
78+ // Pre-run simulation to completion synchronously — no per-tick rendering
8379 const sim = forceSimulation < ForceNode > ( forceNodes )
8480 . force (
8581 "link" ,
86- forceLink < ForceNode , SimulationLinkDatum < ForceNode > > ( forceEdges as SimulationLinkDatum < ForceNode > [ ] )
82+ forceLink < ForceNode , SimulationLinkDatum < ForceNode > > ( forceEdges )
8783 . id ( d => d . id )
8884 . distance ( 80 )
8985 . strength ( 0.2 )
9086 )
9187 . force ( "charge" , forceManyBody ( ) . strength ( - 100 ) )
9288 . force ( "center" , forceCenter ( canvas . clientWidth / 2 , canvas . clientHeight / 2 ) )
93- . force ( "collide" , forceCollide ( 15 ) ) ;
89+ . force ( "collide" , forceCollide ( 15 ) )
90+ . stop ( ) ; // Don't auto-run
91+
92+ // Run 300 ticks synchronously (no rendering in between)
93+ for ( let i = 0 ; i < 300 ; i ++ ) {
94+ sim . tick ( ) ;
95+ }
96+
97+ // Build node lookup map for O(1) edge resolution
98+ const nodeMap = new Map ( forceNodes . map ( n => [ n . id , n ] ) ) ;
99+
100+ // Draw once — edges
101+ const edgesContainer = new Container ( ) ;
102+ const nodesContainer = new Container ( ) ;
103+ app . stage . addChild ( edgesContainer ) ;
104+ app . stage . addChild ( nodesContainer ) ;
94105
95- sim . on ( "tick" , ( ) => {
96- edgesContainer . removeChildren ( ) ;
97- nodesContainer . removeChildren ( ) ;
106+ const edgeGfx = new Graphics ( ) ;
107+ for ( const edge of forceEdges ) {
108+ const sourceNode = edge . source as unknown as ForceNode ;
109+ const targetNode = edge . target as unknown as ForceNode ;
110+ if ( ! sourceNode . x || ! sourceNode . y || ! targetNode . x || ! targetNode . y ) continue ;
98111
99- // Draw edges as synapses
100- const edgeGfx = new Graphics ( ) ;
101- for ( const edge of forceEdges ) {
102- const s = forceNodes . find ( n => n . id === ( edge . source as unknown as ForceNode ) . id || n . id === edge . source ) ;
103- const t = forceNodes . find ( n => n . id === ( edge . target as unknown as ForceNode ) . id || n . id === edge . target ) ;
104- if ( ! s ?. x || ! s ?. y || ! t ?. x || ! t ?. y ) continue ;
112+ const color = ( edge as { type ?: string } ) . type === "category" ? 0x7c3aed : 0x22d3ee ;
113+ edgeGfx . moveTo ( sourceNode . x , sourceNode . y ) ;
114+ edgeGfx . lineTo ( targetNode . x , targetNode . y ) ;
115+ edgeGfx . stroke ( { width : 1 , color, alpha : 0.12 } ) ;
116+ }
117+ edgesContainer . addChild ( edgeGfx ) ;
118+
119+ // Draw once — nodes
120+ for ( const node of forceNodes ) {
121+ if ( ! node . x || ! node . y ) continue ;
122+
123+ const radius = 4 + node . strength * 8 ;
124+ const colorHex = node . layer === "sml" ? 0x22d3ee : 0xfbbf24 ;
125+
126+ const gfx = new Graphics ( ) ;
127+ // Glow
128+ gfx . circle ( 0 , 0 , radius * 2.5 ) ;
129+ gfx . fill ( { color : colorHex , alpha : 0.05 } ) ;
130+ // Node
131+ gfx . circle ( 0 , 0 , radius ) ;
132+ gfx . fill ( { color : colorHex , alpha : 0.8 } ) ;
133+
134+ gfx . position . set ( node . x , node . y ) ;
135+ gfx . eventMode = "static" ;
136+ gfx . cursor = "pointer" ;
137+
138+ gfx . on ( "pointerdown" , ( e : FederatedPointerEvent ) => {
139+ e . stopPropagation ( ) ;
140+ openInspector ( node . id ) ;
141+ } ) ;
142+ gfx . on ( "pointerover" , ( ) => gfx . scale . set ( 1.4 ) ) ;
143+ gfx . on ( "pointerout" , ( ) => gfx . scale . set ( 1 ) ) ;
105144
106- const color = edge . type === "category" ? 0x7c3aed : 0x22d3ee ;
107- const alpha = 0.12 ;
145+ nodesContainer . addChild ( gfx ) ;
108146
109- edgeGfx . moveTo ( s . x , s . y ) ;
110- edgeGfx . lineTo ( t . x , t . y ) ;
111- edgeGfx . stroke ( { width : 1 , color, alpha } ) ;
112- }
113- edgesContainer . addChild ( edgeGfx ) ;
114-
115- // Draw nodes
116- for ( const node of forceNodes ) {
117- if ( ! node . x || ! node . y ) continue ;
118-
119- const radius = 4 + node . strength * 8 ;
120- const colorHex = node . layer === "sml" ? 0x22d3ee : 0xfbbf24 ;
121-
122- const gfx = new Graphics ( ) ;
123- // Glow
124- gfx . circle ( 0 , 0 , radius * 2.5 ) ;
125- gfx . fill ( { color : colorHex , alpha : 0.05 } ) ;
126- // Node
127- gfx . circle ( 0 , 0 , radius ) ;
128- gfx . fill ( { color : colorHex , alpha : 0.8 } ) ;
129-
130- gfx . position . set ( node . x , node . y ) ;
131- gfx . eventMode = "static" ;
132- gfx . cursor = "pointer" ;
133-
134- gfx . on ( "pointerdown" , ( e : FederatedPointerEvent ) => {
135- e . stopPropagation ( ) ;
136- openInspector ( node . id ) ;
147+ // Label only for strong nodes
148+ if ( node . strength > 0.5 && node . label ) {
149+ const label = new Text ( {
150+ text : node . label . slice ( 0 , 25 ) ,
151+ style : new TextStyle ( { fontSize : 8 , fill : 0x94a3b8 , fontFamily : "system-ui" } ) ,
137152 } ) ;
138- gfx . on ( "pointerover" , ( ) => gfx . scale . set ( 1.4 ) ) ;
139- gfx . on ( "pointerout" , ( ) => gfx . scale . set ( 1 ) ) ;
140-
141- nodesContainer . addChild ( gfx ) ;
142-
143- // Label
144- if ( node . strength > 0.5 && node . label ) {
145- const label = new Text ( {
146- text : node . label . slice ( 0 , 25 ) ,
147- style : new TextStyle ( { fontSize : 8 , fill : 0x94a3b8 , fontFamily : "system-ui" } ) ,
148- } ) ;
149- label . anchor . set ( 0.5 , 0 ) ;
150- label . position . set ( node . x , node . y + radius + 4 ) ;
151- nodesContainer . addChild ( label ) ;
152- }
153+ label . anchor . set ( 0.5 , 0 ) ;
154+ label . position . set ( node . x , node . y + radius + 4 ) ;
155+ nodesContainer . addChild ( label ) ;
153156 }
154- } ) ;
155-
156- // Stop after 200 ticks
157- sim . alpha ( 1 ) . alphaDecay ( 0.005 ) ;
157+ }
158158 } , [ data , openInspector ] ) ;
159159
160160 useEffect ( ( ) => {
0 commit comments