@@ -247,33 +247,165 @@ export function InteractiveGraphVisualization() {
247247 if ( ! svgRef . current ) return ;
248248
249249 const svg = d3 . select ( svgRef . current ) ;
250+ const defs = svg . select ( 'defs' ) ;
250251
251252 // Remove glow and reset properties from all elements first
252253 svg . selectAll ( '.node-bg' ) . style ( 'filter' , null ) ;
253254 svg . selectAll ( '.edge' )
254255 . style ( 'filter' , null )
255256 . attr ( 'stroke-width' , ( d : any ) => ( d . strength || 0.8 ) * 3 ) ; // Reset to normal thickness
256257
257- // Apply glow to active node if nodeMenu is visible
258+ // Apply type-specific glow to active node if nodeMenu is visible
258259 if ( nodeMenu . visible && nodeMenu . node ) {
260+ const nodeTypeConfig = getTypeConfig ( nodeMenu . node . type as WorkItemType ) ;
261+ const nodeColor = nodeTypeConfig . hexColor ;
262+ const filterId = `node-glow-${ nodeMenu . node . type . toLowerCase ( ) } ` ;
263+
264+ // Remove existing filter and create new one with node's type color
265+ defs . select ( `#${ filterId } ` ) . remove ( ) ;
266+
267+ const nodeGlowFilter = defs . append ( 'filter' )
268+ . attr ( 'id' , filterId )
269+ . attr ( 'x' , '-100%' )
270+ . attr ( 'y' , '-100%' )
271+ . attr ( 'width' , '300%' )
272+ . attr ( 'height' , '300%' ) ;
273+
274+ // Convert hex to RGB values for feColorMatrix
275+ const hexToRgb = ( hex : string ) => {
276+ const result = / ^ # ? ( [ a - f \d ] { 2 } ) ( [ a - f \d ] { 2 } ) ( [ a - f \d ] { 2 } ) $ / i. exec ( hex ) ;
277+ return result ? {
278+ r : parseInt ( result [ 1 ] , 16 ) / 255 ,
279+ g : parseInt ( result [ 2 ] , 16 ) / 255 ,
280+ b : parseInt ( result [ 3 ] , 16 ) / 255
281+ } : { r : 0.06 , g : 0.73 , b : 0.51 } ; // fallback green
282+ } ;
283+
284+ const rgb = hexToRgb ( nodeColor ) ;
285+ nodeGlowFilter . append ( 'feColorMatrix' )
286+ . attr ( 'in' , 'SourceGraphic' )
287+ . attr ( 'type' , 'matrix' )
288+ . attr ( 'values' , `0 0 0 0 ${ rgb . r } 0 0 0 0 ${ rgb . g } 0 0 0 0 ${ rgb . b } 0 0 0 1 0` ) ;
289+
290+ const blur = nodeGlowFilter . append ( 'feGaussianBlur' )
291+ . attr ( 'stdDeviation' , '15' )
292+ . attr ( 'result' , 'coloredBlur' ) ;
293+
294+ blur . append ( 'animate' )
295+ . attr ( 'attributeName' , 'stdDeviation' )
296+ . attr ( 'values' , '10;20;10' )
297+ . attr ( 'dur' , '2s' )
298+ . attr ( 'repeatCount' , 'indefinite' ) ;
299+
300+ const feMerge = nodeGlowFilter . append ( 'feMerge' ) ;
301+ feMerge . append ( 'feMergeNode' ) . attr ( 'in' , 'coloredBlur' ) ;
302+ feMerge . append ( 'feMergeNode' ) . attr ( 'in' , 'SourceGraphic' ) ;
303+
304+ // Apply the type-specific glow filter
259305 svg . selectAll ( '.node-bg' )
260306 . filter ( ( d : any ) => d && d . id === nodeMenu . node ?. id )
261- . style ( 'filter' , ' url(#dialog-glow)' ) ;
307+ . style ( 'filter' , ` url(#${ filterId } )` ) ;
262308 }
263309
264- // Apply stronger glow to active edge if editingEdge OR showEdgeDetails is visible
310+ // Apply relationship-specific glow to active edge if editingEdge is visible
265311 if ( editingEdge && editingEdge . edge ) {
312+ const relationshipConfig = getRelationshipConfig ( editingEdge . edge . type as RelationshipType ) ;
313+ const edgeColor = relationshipConfig . hexColor ;
314+ const edgeFilterId = `edge-glow-${ editingEdge . edge . type . toLowerCase ( ) } ` ;
315+
316+ // Remove existing filter and create new one with relationship color
317+ defs . select ( `#${ edgeFilterId } ` ) . remove ( ) ;
318+
319+ const edgeGlowFilter = defs . append ( 'filter' )
320+ . attr ( 'id' , edgeFilterId )
321+ . attr ( 'x' , '-150%' )
322+ . attr ( 'y' , '-150%' )
323+ . attr ( 'width' , '400%' )
324+ . attr ( 'height' , '400%' ) ;
325+
326+ const hexToRgb = ( hex : string ) => {
327+ const result = / ^ # ? ( [ a - f \d ] { 2 } ) ( [ a - f \d ] { 2 } ) ( [ a - f \d ] { 2 } ) $ / i. exec ( hex ) ;
328+ return result ? {
329+ r : parseInt ( result [ 1 ] , 16 ) / 255 ,
330+ g : parseInt ( result [ 2 ] , 16 ) / 255 ,
331+ b : parseInt ( result [ 3 ] , 16 ) / 255
332+ } : { r : 0.06 , g : 0.73 , b : 0.51 } ; // fallback green
333+ } ;
334+
335+ const rgb = hexToRgb ( edgeColor ) ;
336+ edgeGlowFilter . append ( 'feColorMatrix' )
337+ . attr ( 'in' , 'SourceGraphic' )
338+ . attr ( 'type' , 'matrix' )
339+ . attr ( 'values' , `0 0 0 0 ${ rgb . r } 0 0 0 0 ${ rgb . g } 0 0 0 0 ${ rgb . b } 0 0 0 1 0` ) ;
340+
341+ const edgeBlur = edgeGlowFilter . append ( 'feGaussianBlur' )
342+ . attr ( 'stdDeviation' , '25' )
343+ . attr ( 'result' , 'coloredBlur' ) ;
344+
345+ edgeBlur . append ( 'animate' )
346+ . attr ( 'attributeName' , 'stdDeviation' )
347+ . attr ( 'values' , '15;35;15' )
348+ . attr ( 'dur' , '1.5s' )
349+ . attr ( 'repeatCount' , 'indefinite' ) ;
350+
351+ const edgeFeMerge = edgeGlowFilter . append ( 'feMerge' ) ;
352+ edgeFeMerge . append ( 'feMergeNode' ) . attr ( 'in' , 'coloredBlur' ) ;
353+ edgeFeMerge . append ( 'feMergeNode' ) . attr ( 'in' , 'SourceGraphic' ) ;
354+
266355 svg . selectAll ( '.edge' )
267356 . filter ( ( d : any ) => d && d . id === editingEdge . edge ?. id )
268- . style ( 'filter' , ' url(#edge-dialog-glow)' )
357+ . style ( 'filter' , ` url(#${ edgeFilterId } )` )
269358 . attr ( 'stroke-width' , 12 ) ; // Also make the edge thicker
270359 }
271360
272- // Also apply glow to edge when showing edge details
361+ // Also apply relationship-specific glow to edge when showing edge details
273362 if ( showEdgeDetails && selectedEdge ) {
363+ const relationshipConfig = getRelationshipConfig ( selectedEdge . type as RelationshipType ) ;
364+ const edgeColor = relationshipConfig . hexColor ;
365+ const edgeFilterId = `edge-glow-${ selectedEdge . type . toLowerCase ( ) } ` ;
366+
367+ // Remove existing filter and create new one with relationship color
368+ defs . select ( `#${ edgeFilterId } ` ) . remove ( ) ;
369+
370+ const edgeGlowFilter = defs . append ( 'filter' )
371+ . attr ( 'id' , edgeFilterId )
372+ . attr ( 'x' , '-150%' )
373+ . attr ( 'y' , '-150%' )
374+ . attr ( 'width' , '400%' )
375+ . attr ( 'height' , '400%' ) ;
376+
377+ const hexToRgb = ( hex : string ) => {
378+ const result = / ^ # ? ( [ a - f \d ] { 2 } ) ( [ a - f \d ] { 2 } ) ( [ a - f \d ] { 2 } ) $ / i. exec ( hex ) ;
379+ return result ? {
380+ r : parseInt ( result [ 1 ] , 16 ) / 255 ,
381+ g : parseInt ( result [ 2 ] , 16 ) / 255 ,
382+ b : parseInt ( result [ 3 ] , 16 ) / 255
383+ } : { r : 0.06 , g : 0.73 , b : 0.51 } ; // fallback green
384+ } ;
385+
386+ const rgb = hexToRgb ( edgeColor ) ;
387+ edgeGlowFilter . append ( 'feColorMatrix' )
388+ . attr ( 'in' , 'SourceGraphic' )
389+ . attr ( 'type' , 'matrix' )
390+ . attr ( 'values' , `0 0 0 0 ${ rgb . r } 0 0 0 0 ${ rgb . g } 0 0 0 0 ${ rgb . b } 0 0 0 1 0` ) ;
391+
392+ const edgeBlur = edgeGlowFilter . append ( 'feGaussianBlur' )
393+ . attr ( 'stdDeviation' , '25' )
394+ . attr ( 'result' , 'coloredBlur' ) ;
395+
396+ edgeBlur . append ( 'animate' )
397+ . attr ( 'attributeName' , 'stdDeviation' )
398+ . attr ( 'values' , '15;35;15' )
399+ . attr ( 'dur' , '1.5s' )
400+ . attr ( 'repeatCount' , 'indefinite' ) ;
401+
402+ const edgeFeMerge = edgeGlowFilter . append ( 'feMerge' ) ;
403+ edgeFeMerge . append ( 'feMergeNode' ) . attr ( 'in' , 'coloredBlur' ) ;
404+ edgeFeMerge . append ( 'feMergeNode' ) . attr ( 'in' , 'SourceGraphic' ) ;
405+
274406 svg . selectAll ( '.edge' )
275407 . filter ( ( d : any ) => d && d . id === selectedEdge . id )
276- . style ( 'filter' , ' url(#edge-dialog-glow)' )
408+ . style ( 'filter' , ` url(#${ edgeFilterId } )` )
277409 . attr ( 'stroke-width' , 12 ) ; // Same thickness as relationship editing
278410 }
279411 } , [ nodeMenu . visible , nodeMenu . node ?. id , editingEdge ?. edge ?. id , showEdgeDetails , selectedEdge ?. id ] ) ;
@@ -1079,10 +1211,7 @@ export function InteractiveGraphVisualization() {
10791211 return classes ;
10801212 } )
10811213 . attr ( 'stroke' , ( d : WorkItemEdge ) => {
1082- // Force bright green for active dialog
1083- if ( editingEdge && editingEdge . edge && editingEdge . edge . id === d . id ) {
1084- return '#10b981' ; // Bright green
1085- }
1214+ // Use relationship color for active dialog (same as normal)
10861215 const config = getRelationshipConfig ( d . type as RelationshipType ) ;
10871216 return config . hexColor ;
10881217 } )
@@ -1163,65 +1292,6 @@ export function InteractiveGraphVisualization() {
11631292 // Add arrowhead markers for middle of edges
11641293 const defs = svg . append ( 'defs' ) ;
11651294
1166- // Create pulsing glow filter for active dialogs (nodes)
1167- const glowFilter = defs . append ( 'filter' )
1168- . attr ( 'id' , 'dialog-glow' )
1169- . attr ( 'x' , '-100%' )
1170- . attr ( 'y' , '-100%' )
1171- . attr ( 'width' , '300%' )
1172- . attr ( 'height' , '300%' ) ;
1173-
1174- // Create a bright green glow that pulses
1175- glowFilter . append ( 'feColorMatrix' )
1176- . attr ( 'in' , 'SourceGraphic' )
1177- . attr ( 'type' , 'matrix' )
1178- . attr ( 'values' , '0 0 0 0 0.06 0 0 0 0 0.73 0 0 0 0 0.51 0 0 0 1 0' ) ; // bright green
1179-
1180- const blur = glowFilter . append ( 'feGaussianBlur' )
1181- . attr ( 'stdDeviation' , '15' )
1182- . attr ( 'result' , 'coloredBlur' ) ;
1183-
1184- // Add animation to the blur intensity for pulsing effect
1185- blur . append ( 'animate' )
1186- . attr ( 'attributeName' , 'stdDeviation' )
1187- . attr ( 'values' , '10;20;10' )
1188- . attr ( 'dur' , '2s' )
1189- . attr ( 'repeatCount' , 'indefinite' ) ;
1190-
1191- // Merge blur with original
1192- const feMerge = glowFilter . append ( 'feMerge' ) ;
1193- feMerge . append ( 'feMergeNode' ) . attr ( 'in' , 'coloredBlur' ) ;
1194- feMerge . append ( 'feMergeNode' ) . attr ( 'in' , 'SourceGraphic' ) ;
1195-
1196- // Create stronger glow filter specifically for edges
1197- const edgeGlowFilter = defs . append ( 'filter' )
1198- . attr ( 'id' , 'edge-dialog-glow' )
1199- . attr ( 'x' , '-150%' )
1200- . attr ( 'y' , '-150%' )
1201- . attr ( 'width' , '400%' )
1202- . attr ( 'height' , '400%' ) ;
1203-
1204- // Much stronger glow for edges
1205- edgeGlowFilter . append ( 'feColorMatrix' )
1206- . attr ( 'in' , 'SourceGraphic' )
1207- . attr ( 'type' , 'matrix' )
1208- . attr ( 'values' , '0 0 0 0 0.06 0 0 0 0 0.73 0 0 0 0 0.51 0 0 0 1 0' ) ; // bright green
1209-
1210- const edgeBlur = edgeGlowFilter . append ( 'feGaussianBlur' )
1211- . attr ( 'stdDeviation' , '25' )
1212- . attr ( 'result' , 'coloredBlur' ) ;
1213-
1214- // Stronger pulsing animation for edges
1215- edgeBlur . append ( 'animate' )
1216- . attr ( 'attributeName' , 'stdDeviation' )
1217- . attr ( 'values' , '15;35;15' )
1218- . attr ( 'dur' , '1.5s' )
1219- . attr ( 'repeatCount' , 'indefinite' ) ;
1220-
1221- // Merge blur with original for edges
1222- const edgeFeMerge = edgeGlowFilter . append ( 'feMerge' ) ;
1223- edgeFeMerge . append ( 'feMergeNode' ) . attr ( 'in' , 'coloredBlur' ) ;
1224- edgeFeMerge . append ( 'feMergeNode' ) . attr ( 'in' , 'SourceGraphic' ) ;
12251295
12261296 // Create different arrowhead colors for each edge type
12271297 RELATIONSHIP_OPTIONS . forEach ( ( option ) => {
@@ -1437,13 +1507,15 @@ export function InteractiveGraphVisualization() {
14371507 return '#1f2937' ; // Dark background consistent with theme
14381508 } )
14391509 . attr ( 'stroke' , ( d : WorkItem ) => {
1440- // Force bright green with glow for active dialog
1510+ // Use node type color for active dialog
14411511 if ( nodeMenu . visible && nodeMenu . node && nodeMenu . node . id === d . id ) {
1442- return '#10b981' ; // Bright green
1512+ const typeConfig = getTypeConfig ( d . type as WorkItemType ) ;
1513+ return typeConfig . hexColor ;
14431514 }
1444- // Highlight selected node with bright border
1515+ // Highlight selected node with type color border
14451516 if ( selectedNode && selectedNode . id === d . id ) {
1446- return '#10b981' ; // Bright green for selected node
1517+ const typeConfig = getTypeConfig ( d . type as WorkItemType ) ;
1518+ return typeConfig . hexColor ;
14471519 }
14481520 if ( d . status === 'COMPLETED' || d . status === 'Completed' || d . status === 'Done' || d . status === 'DONE' ) {
14491521 return '#4b5563' ;
@@ -1855,22 +1927,12 @@ export function InteractiveGraphVisualization() {
18551927 . attr ( 'class' , 'arrow' )
18561928 . attr ( 'd' , 'M-16,-8 L0,0 L-16,8 L-8,0 Z' )
18571929 . attr ( 'fill' , ( d : WorkItemEdge ) => {
1858- // Use the source node's color for the arrow
1859- const sourceNode = nodes . find ( n => n . id === ( typeof d . source === 'string' ? d . source : ( d . source as any ) ?. id ) ) ;
1860- if ( sourceNode ) {
1861- return getNodeColor ( sourceNode ) ;
1862- }
1863- // Fallback to edge type color if node not found
1930+ // Use the relationship color for consistency with edge stroke
18641931 const config = getRelationshipConfig ( d . type as RelationshipType ) ;
18651932 return config . hexColor ;
18661933 } )
18671934 . attr ( 'stroke' , ( d : WorkItemEdge ) => {
1868- // Use the source node's color for the arrow stroke
1869- const sourceNode = nodes . find ( n => n . id === ( typeof d . source === 'string' ? d . source : ( d . source as any ) ?. id ) ) ;
1870- if ( sourceNode ) {
1871- return getNodeColor ( sourceNode ) ;
1872- }
1873- // Fallback to edge type color if node not found
1935+ // Always use relationship color for consistency
18741936 const config = getRelationshipConfig ( d . type as RelationshipType ) ;
18751937 return config . hexColor ;
18761938 } )
0 commit comments