@@ -138,8 +138,8 @@ export default function RelationGraph() {
138138 const canvas = canvasRef . current ! ;
139139 const rect = canvas . getBoundingClientRect ( ) ;
140140 return {
141- cx : ( e . clientX - rect . left ) * ( canvas . width / rect . width ) ,
142- cy : ( e . clientY - rect . top ) * ( canvas . height / rect . height ) ,
141+ cx : ( e . clientX - rect . left ) * ( canvas . width / rect . width ) / dprRef . current ,
142+ cy : ( e . clientY - rect . top ) * ( canvas . height / rect . height ) / dprRef . current ,
143143 } ;
144144 } ;
145145
@@ -180,14 +180,17 @@ export default function RelationGraph() {
180180 const graphW = maxX - minX + padding * 2 ;
181181 const graphH = maxY - minY + padding * 2 ;
182182
183- const scale = Math . min ( canvas . width / graphW , canvas . height / graphH , 2.5 ) ;
183+ const dpr = dprRef . current ;
184+ const cw = canvas . width / dpr ;
185+ const ch = canvas . height / dpr ;
186+ const scale = Math . min ( cw / graphW , ch / graphH , 2.5 ) ;
184187 const centerX = ( minX + maxX ) / 2 ;
185188 const centerY = ( minY + maxY ) / 2 ;
186189
187190 transformRef . current = {
188191 scale,
189- offsetX : canvas . width / 2 - centerX * scale ,
190- offsetY : canvas . height / 2 - centerY * scale ,
192+ offsetX : cw / 2 - centerX * scale ,
193+ offsetY : ch / 2 - centerY * scale ,
191194 } ;
192195 } , [ ] ) ;
193196
@@ -200,8 +203,9 @@ export default function RelationGraph() {
200203 const ctx = canvas . getContext ( '2d' ) ;
201204 if ( ! ctx ) return ;
202205
203- const W = canvas . width ;
204- const H = canvas . height ;
206+ const dpr = dprRef . current ;
207+ const W = canvas . width / dpr ;
208+ const H = canvas . height / dpr ;
205209 const nodes = nodesRef . current ;
206210 const sel = selectedRef . current ;
207211 const cam = transformRef . current ;
@@ -375,6 +379,24 @@ export default function RelationGraph() {
375379 // Keep selectedRef in sync
376380 useEffect ( ( ) => { selectedRef . current = selectedNode ; } , [ selectedNode ] ) ;
377381
382+ // Set up HiDPI canvas
383+ const dprRef = useRef ( 1 ) ;
384+ const canvasInitRef = useRef ( false ) ;
385+ useEffect ( ( ) => {
386+ const canvas = canvasRef . current ;
387+ if ( ! canvas || canvasInitRef . current ) return ;
388+ canvasInitRef . current = true ;
389+ const dpr = window . devicePixelRatio || 1 ;
390+ dprRef . current = dpr ;
391+ const w = 900 , h = 500 ;
392+ canvas . width = w * dpr ;
393+ canvas . height = h * dpr ;
394+ canvas . style . width = '100%' ;
395+ canvas . style . height = 'auto' ;
396+ const ctx = canvas . getContext ( '2d' ) ;
397+ if ( ctx ) ctx . scale ( dpr , dpr ) ;
398+ } , [ ] ) ;
399+
378400 useEffect ( ( ) => {
379401 if ( filteredRelations . length > 0 ) {
380402 alphaRef . current = 1.0 ; // reheat on data change
@@ -507,8 +529,8 @@ export default function RelationGraph() {
507529 if ( ! canvas ) return ;
508530
509531 const rect = canvas . getBoundingClientRect ( ) ;
510- const cx = ( e . clientX - rect . left ) * ( canvas . width / rect . width ) ;
511- const cy = ( e . clientY - rect . top ) * ( canvas . height / rect . height ) ;
532+ const cx = ( e . clientX - rect . left ) * ( canvas . width / rect . width ) / dprRef . current ;
533+ const cy = ( e . clientY - rect . top ) * ( canvas . height / rect . height ) / dprRef . current ;
512534
513535 const t = transformRef . current ;
514536 const zoomFactor = e . deltaY < 0 ? 1.12 : 1 / 1.12 ;
@@ -532,8 +554,8 @@ export default function RelationGraph() {
532554 const handler = ( e : WheelEvent ) => {
533555 e . preventDefault ( ) ;
534556 const rect = canvas . getBoundingClientRect ( ) ;
535- const cx = ( e . clientX - rect . left ) * ( canvas . width / rect . width ) ;
536- const cy = ( e . clientY - rect . top ) * ( canvas . height / rect . height ) ;
557+ const cx = ( e . clientX - rect . left ) * ( canvas . width / rect . width ) / ( window . devicePixelRatio || 1 ) ;
558+ const cy = ( e . clientY - rect . top ) * ( canvas . height / rect . height ) / ( window . devicePixelRatio || 1 ) ;
537559
538560 const t = transformRef . current ;
539561 const zoomFactor = e . deltaY < 0 ? 1.12 : 1 / 1.12 ;
@@ -564,19 +586,18 @@ export default function RelationGraph() {
564586 } ;
565587
566588 const getTouchCenter = ( touches : React . TouchList ) => {
589+ const canvas = canvasRef . current ! ;
590+ const rect = canvas . getBoundingClientRect ( ) ;
591+ const dpr = dprRef . current ;
567592 if ( touches . length < 2 ) {
568- const canvas = canvasRef . current ! ;
569- const rect = canvas . getBoundingClientRect ( ) ;
570593 return {
571- cx : ( touches [ 0 ] ! . clientX - rect . left ) * ( canvas . width / rect . width ) ,
572- cy : ( touches [ 0 ] ! . clientY - rect . top ) * ( canvas . height / rect . height ) ,
594+ cx : ( touches [ 0 ] ! . clientX - rect . left ) * ( canvas . width / rect . width ) / dpr ,
595+ cy : ( touches [ 0 ] ! . clientY - rect . top ) * ( canvas . height / rect . height ) / dpr ,
573596 } ;
574597 }
575- const canvas = canvasRef . current ! ;
576- const rect = canvas . getBoundingClientRect ( ) ;
577598 return {
578- cx : ( ( touches [ 0 ] ! . clientX + touches [ 1 ] ! . clientX ) / 2 - rect . left ) * ( canvas . width / rect . width ) ,
579- cy : ( ( touches [ 0 ] ! . clientY + touches [ 1 ] ! . clientY ) / 2 - rect . top ) * ( canvas . height / rect . height ) ,
599+ cx : ( ( touches [ 0 ] ! . clientX + touches [ 1 ] ! . clientX ) / 2 - rect . left ) * ( canvas . width / rect . width ) / dpr ,
600+ cy : ( ( touches [ 0 ] ! . clientY + touches [ 1 ] ! . clientY ) / 2 - rect . top ) * ( canvas . height / rect . height ) / dpr ,
580601 } ;
581602 } ;
582603
@@ -692,7 +713,7 @@ export default function RelationGraph() {
692713 ref = { canvasRef }
693714 width = { 900 }
694715 height = { 500 }
695- style = { { width : '100%' , height : 'auto' , background : '#0f0f1a' , borderRadius : 8 , cursor : dragRef . current . nodeId ? 'grabbing' : dragRef . current . isPanning ? 'grabbing' : 'grab' , touchAction : 'none' } }
716+ style = { { width : '100%' , height : 'auto' , background : '#0f0f1a' , borderRadius : 8 , cursor : dragRef . current . nodeId ? 'grabbing' : dragRef . current . isPanning ? 'grabbing' : 'grab' , touchAction : 'none' , imageRendering : 'auto' } }
696717 onMouseDown = { onMouseDown }
697718 onMouseMove = { onMouseMove }
698719 onMouseUp = { onMouseUp }
@@ -705,9 +726,9 @@ export default function RelationGraph() {
705726 { /* Zoom controls - bottom right */ }
706727 < div style = { { position : 'absolute' , bottom : 40 , right : 16 , display : 'flex' , gap : 4 , background : 'rgba(15,15,26,0.8)' , borderRadius : 6 , padding : 4 } } >
707728 < button style = { { width : 28 , height : 28 , border : '1px solid rgba(255,255,255,0.15)' , borderRadius : 4 , background : 'rgba(255,255,255,0.08)' , color : '#ccc' , fontSize : 16 , cursor : 'pointer' , display : 'flex' , alignItems : 'center' , justifyContent : 'center' } }
708- onClick = { ( ) => { const t = transformRef . current ; const cx = canvasRef . current ! . width / 2 ; const cy = canvasRef . current ! . height / 2 ; const wx = ( cx - t . offsetX ) / t . scale ; const wy = ( cy - t . offsetY ) / t . scale ; const ns = Math . min ( t . scale * 1.3 , 8 ) ; transformRef . current = { scale : ns , offsetX : cx - wx * ns , offsetY : cy - wy * ns } ; } } > +</ button >
729+ onClick = { ( ) => { const t = transformRef . current ; const cx = canvasRef . current ! . width / 2 / dprRef . current ; const cy = canvasRef . current ! . height / 2 / dprRef . current ; const wx = ( cx - t . offsetX ) / t . scale ; const wy = ( cy - t . offsetY ) / t . scale ; const ns = Math . min ( t . scale * 1.3 , 8 ) ; transformRef . current = { scale : ns , offsetX : cx - wx * ns , offsetY : cy - wy * ns } ; } } > +</ button >
709730 < button style = { { width : 28 , height : 28 , border : '1px solid rgba(255,255,255,0.15)' , borderRadius : 4 , background : 'rgba(255,255,255,0.08)' , color : '#ccc' , fontSize : 16 , cursor : 'pointer' , display : 'flex' , alignItems : 'center' , justifyContent : 'center' } }
710- onClick = { ( ) => { const t = transformRef . current ; const cx = canvasRef . current ! . width / 2 ; const cy = canvasRef . current ! . height / 2 ; const wx = ( cx - t . offsetX ) / t . scale ; const wy = ( cy - t . offsetY ) / t . scale ; const ns = Math . max ( t . scale / 1.3 , 0.1 ) ; transformRef . current = { scale : ns , offsetX : cx - wx * ns , offsetY : cy - wy * ns } ; } } > β</ button >
731+ onClick = { ( ) => { const t = transformRef . current ; const cx = canvasRef . current ! . width / 2 / dprRef . current ; const cy = canvasRef . current ! . height / 2 / dprRef . current ; const wx = ( cx - t . offsetX ) / t . scale ; const wy = ( cy - t . offsetY ) / t . scale ; const ns = Math . max ( t . scale / 1.3 , 0.1 ) ; transformRef . current = { scale : ns , offsetX : cx - wx * ns , offsetY : cy - wy * ns } ; } } > β</ button >
711732 < button style = { { width : 28 , height : 28 , border : '1px solid rgba(255,255,255,0.15)' , borderRadius : 4 , background : 'rgba(255,255,255,0.08)' , color : '#ccc' , fontSize : 11 , cursor : 'pointer' , display : 'flex' , alignItems : 'center' , justifyContent : 'center' } }
712733 onClick = { ( ) => { fitToView ( ) ; alphaRef . current = Math . max ( alphaRef . current , 0.3 ) ; } } > Fit</ button >
713734 </ div >
0 commit comments