@@ -273,38 +273,77 @@ export default function GraphView({
273273
274274 if ( ! start . x || ! start . y || ! end . x || ! end . y ) return
275275
276+ let textX , textY , angle ;
277+
276278 if ( start . id === end . id ) {
277279 const radius = NODE_SIZE * link . curve * 6.2 ;
278280 const angleOffset = - Math . PI / 4 ; // 45 degrees offset for text alignment
279- const textX = start . x + radius * Math . cos ( angleOffset ) ;
280- const textY = start . y + radius * Math . sin ( angleOffset ) ;
281-
282- ctx . save ( ) ;
283- ctx . translate ( textX , textY ) ;
284- ctx . rotate ( - angleOffset ) ;
281+ textX = start . x + radius * Math . cos ( angleOffset ) ;
282+ textY = start . y + radius * Math . sin ( angleOffset ) ;
283+ angle = - angleOffset ;
285284 } else {
286- const midX = ( start . x + end . x ) / 2 + ( end . y - start . y ) * ( link . curve / 2 ) ;
287- const midY = ( start . y + end . y ) / 2 + ( start . x - end . x ) * ( link . curve / 2 ) ;
285+ const midX = ( start . x + end . x ) / 2 ;
286+ const midY = ( start . y + end . y ) / 2 ;
287+ const offset = link . curve / 2 ;
288288
289- let textAngle = Math . atan2 ( end . y - start . y , end . x - start . x )
289+ angle = Math . atan2 ( end . y - start . y , end . x - start . x ) ;
290290
291291 // maintain label vertical orientation for legibility
292- if ( textAngle > Math . PI / 2 ) textAngle = - ( Math . PI - textAngle ) ;
293- if ( textAngle < - Math . PI / 2 ) textAngle = - ( - Math . PI - textAngle ) ;
292+ if ( angle > Math . PI / 2 ) angle = - ( Math . PI - angle ) ;
293+ if ( angle < - Math . PI / 2 ) angle = - ( - Math . PI - angle ) ;
294+
295+ // Calculate perpendicular offset
296+ const perpX = - Math . sin ( angle ) * offset ;
297+ const perpY = Math . cos ( angle ) * offset ;
298+
299+ // Adjust position to compensate for rotation around origin
300+ const cos = Math . cos ( angle ) ;
301+ const sin = Math . sin ( angle ) ;
302+ textX = midX + perpX ;
303+ textY = midY + perpY ;
304+ const rotatedX = textX * cos + textY * sin ;
305+ const rotatedY = - textX * sin + textY * cos ;
306+ textX = rotatedX ;
307+ textY = rotatedY ;
308+ }
294309
295- ctx . save ( ) ;
296- ctx . translate ( midX , midY ) ;
297- ctx . rotate ( textAngle ) ;
310+ // Setup text properties to measure background size
311+ ctx . font = '2px Arial' ;
312+ const padding = 0.5 ;
313+ // Get text width and height
314+ const label = graph . LabelsMap . get ( link . label ) !
315+ let { textWidth, textHeight } = label
316+
317+ if ( ! textWidth || ! textHeight ) {
318+ const { width, actualBoundingBoxAscent, actualBoundingBoxDescent } = ctx . measureText ( link . label )
319+ textWidth = width
320+ textHeight = actualBoundingBoxAscent + actualBoundingBoxDescent
321+ graph . LabelsMap . set ( link . label , { ...label , textWidth, textHeight } )
298322 }
299323
300- // add label
324+ // Save the current context state
325+ ctx . save ( ) ;
326+
327+ // add label with background and rotation
328+ ctx . rotate ( angle ) ;
329+
330+ // Draw background
331+ ctx . fillStyle = 'white' ;
332+ ctx . fillRect (
333+ textX - textWidth / 2 - padding ,
334+ textY - textHeight / 2 - padding ,
335+ textWidth + padding * 2 ,
336+ textHeight + padding * 2
337+ ) ;
338+
339+ // Draw text
301340 ctx . globalAlpha = 1 ;
302341 ctx . fillStyle = 'black' ;
303342 ctx . textAlign = 'center' ;
304343 ctx . textBaseline = 'middle' ;
305- ctx . font = '2px Arial' ;
306- ctx . fillText ( link . label , 0 , 0 ) ;
307- ctx . restore ( )
344+ ctx . fillText ( link . label , textX , textY ) ;
345+
346+ ctx . restore ( ) ; // reset rotation
308347 } }
309348 onNodeClick = { screenSize > Number ( process . env . NEXT_PUBLIC_MOBILE_BREAKPOINT ) || isShowPath ? handleNodeClick : handleRightClick }
310349 onNodeRightClick = { handleRightClick }
0 commit comments