@@ -106,8 +106,8 @@ <h2 style="text-align: center;">
106106 arrowhead_size = link_stroke_width * 1.75 ,
107107 circle_radius = { { params . circle_radius } } ,
108108 title_circle_radius = circle_radius * 1.2 ,
109- node_stroke_width = 0 ,
110- node_stroke = "gray "
109+ node_stroke_width = 1 ,
110+ node_stroke = "black "
111111
112112 const svg = d3 . select ( "svg" )
113113 . attr ( "width" , width )
@@ -173,18 +173,8 @@ <h2 style="text-align: center;">
173173 const defs = svg . insert ( "defs" , ":first-child" ) ;
174174 const arrowhead_size = link_stroke_width * 1.5 ;
175175 for ( let i = 1 ; i <= { { groups| length } } ; i ++ ) {
176- defs . append ( "marker" )
177- . attr ( "id" , `head-${ i } ` )
178- . attr ( "viewBox" , `0 -${ arrowhead_size } ${ arrowhead_size * 2 } ${ arrowhead_size * 2 } ` )
179- . attr ( "markerUnits" , "userSpaceOnUse" )
180- . attr ( "refX" , circle_radius + arrowhead_size * 2 - link_stroke_width )
181- . attr ( "refY" , 0 )
182- . attr ( "markerWidth" , arrowhead_size * 2 )
183- . attr ( "markerHeight" , arrowhead_size * 2 )
184- . attr ( "orient" , "auto" )
185- . append ( "path" )
186- . attr ( "d" , `M 0 -${ arrowhead_size } L ${ arrowhead_size * 2 } 0 L 0 ${ arrowhead_size } ` )
187- . attr ( "fill" , color ( i ) ) ;
176+ makeMarker ( `head-${ i } ` , color ( i ) ) ; // normal
177+ makeMarker ( `head-desat-${ i } ` , desaturate ( color ( i ) ) ) ; // desaturated
188178 }
189179
190180 // Creates the links that appear
@@ -235,10 +225,9 @@ <h2 style="text-align: center;">
235225 node . append ( "title" )
236226 . text ( d => d . id ) ;
237227
228+ // make everything a bit transparent
238229 node . on ( "mouseover" , function ( d ) {
239230 d = d . srcElement . __data__ ;
240- circles
241- . attr ( "opacity" , 0.2 ) ;
242231
243232 let marked = new Set ( ) ,
244233 queue = [ ] ;
@@ -249,34 +238,95 @@ <h2 style="text-align: center;">
249238 marked . add ( id0 ) ;
250239 queue = [ ...queue , ...nodeById . get ( id0 ) . bk ] ;
251240 }
252- circles
253- . filter ( d2 => marked . has ( d2 . id ) )
254- . attr ( "opacity" , 1 ) ;
255- circles
256- . filter ( d2 => d2 . id === d . id )
257- . attr ( "stroke" , "black" )
258- . attr ( "stroke-width" , 3 ) ;
259- link . filter ( l => {
260- return ! marked . has ( l . source . id ) || ! marked . has ( l . target . id ) ;
261- } )
262- . attr ( "opacity" , 0.05 ) ;
263- svg . selectAll ( "text" )
264- . filter ( d2 => d2 . group !== d . group )
265- . attr ( "opacity" , 0.5 ) ;
266- svg . selectAll ( "text" )
267- . filter ( d2 => d2 . id === d . id )
268- . attr ( "font-weight" , "bold" ) ;
241+
242+ circles . each ( function ( d2 ) {
243+ const sel = d3 . select ( this ) ;
244+ if ( marked . has ( d2 . id ) ) {
245+ sel . attr ( "fill" , color ( d2 . group ) )
246+ . raise ( ) ;
247+ } else {
248+ sel . attr ( "fill" , desaturate ( color ( d2 . group ) ) )
249+ . lower ( ) ;
250+ }
251+ if ( d2 . id == d . id ) {
252+ sel . attr ( "stroke" , "black" )
253+ . attr ( "stroke-width" , Math . max ( 1 , node_stroke_width ) * 3 ) ;
254+ }
255+ } ) ;
256+ link . each ( function ( l ) {
257+ const sel = d3 . select ( this ) ;
258+ const g = nodeById . get ( l . source . id ) . group ;
259+ if ( marked . has ( l . source . id ) && marked . has ( l . target . id ) ) {
260+ sel . attr ( "stroke" , color ( g ) )
261+ . attr ( "marker-end" , `url(#head-${ g } )` )
262+ . raise ( ) ;
263+ } else {
264+ sel . attr ( "stroke" , desaturate ( color ( g ) ) )
265+ . attr ( "marker-end" , `url(#head-desat-${ g } )` )
266+ . lower ( ) ;
267+ }
268+ } ) ;
269+ labels . each ( function ( d2 ) {
270+ const sel = d3 . select ( this ) ;
271+ if ( marked . has ( d2 . id ) ) {
272+ sel . attr ( "fill" , "black" )
273+ . raise ( ) ;
274+ } else {
275+ sel . attr ( "fill" , "#888" )
276+ . lower ( ) ;
277+ }
278+ } ) ;
279+ } )
280+ . on ( "mouseout" , function ( ) {
281+ circles . attr ( "fill" , d => color ( d . group ) )
282+ . attr ( "stroke" , node_stroke )
283+ . attr ( "stroke-width" , node_stroke_width )
284+ . raise ( ) ;
285+ link . attr ( "stroke" , d => color ( nodeById . get ( d . source . id ) . group ) )
286+ . attr ( "marker-end" , d => `url(#head-${ nodeById . get ( d . source . id ) . group } )` )
287+ . lower ( ) ;
288+ labels . attr ( "fill" , "black" )
289+ . raise ( ) ;
290+ } ) ;
291+
292+ // make everything disappear
293+ /* node.on("mouseover", function(event, d) {
294+ // Collect ancestors
295+ let marked = new Set(),
296+ queue = [d.id];
297+ while (queue.length) {
298+ const id0 = queue.shift();
299+ if (marked.has(id0)) continue;
300+ marked.add(id0);
301+ queue = [...queue, ...nodeById.get(id0).bk];
302+ }
303+
304+ // Hide all circles, then show only ancestors + this node
305+ circles.attr("display", "none");
306+ circles.filter(d2 => marked.has(d2.id))
307+ .attr("display", null); // null restores default (visible)
308+
309+ // Hide all labels, then show only ancestors + this node
310+ labels.attr("display", "none");
311+ labels.filter(d2 => marked.has(d2.id))
312+ .attr("display", null);
313+
314+ // Hide all links, then show only those between ancestors
315+ link.attr("display", "none");
316+ link.filter(l => marked.has(l.source.id) && marked.has(l.target.id))
317+ .attr("display", null);
269318 })
270319 .on("mouseout", function() {
271- svg . selectAll ( "circle" )
272- . attr ( "opacity " , 1 )
320+ // Restore everything
321+ circles .attr("display ", null )
273322 .attr("stroke", node_stroke)
274323 .attr("stroke-width", node_stroke_width);
275- link . attr ( "opacity" , 1 ) ;
276- svg . selectAll ( "text" )
277- . attr ( "opacity" , 1 )
324+
325+ labels.attr("display", null)
278326 .attr("font-weight", "normal");
279- } ) ;
327+
328+ link.attr("display", null);
329+ }); */
280330
281331 simulation . on ( "tick" , ( ) => {
282332 link
@@ -308,6 +358,30 @@ <h2 style="text-align: center;">
308358 }
309359 }
310360
361+ // helpers
362+ function desaturate ( c , amount = 0.9 ) {
363+ let col = d3 . color ( c ) ;
364+ if ( ! col ) return c ;
365+ let hsl = d3 . hsl ( col ) ;
366+ hsl . s *= ( 1 - amount ) ;
367+ return hsl . formatRgb ( ) ;
368+ }
369+
370+ function makeMarker ( id , fillColor ) {
371+ const m = defs . append ( "marker" )
372+ . attr ( "id" , id )
373+ . attr ( "viewBox" , `0 -${ arrowhead_size } ${ arrowhead_size * 2 } ${ arrowhead_size * 2 } ` )
374+ . attr ( "markerUnits" , "userSpaceOnUse" )
375+ . attr ( "refX" , circle_radius + arrowhead_size * 2 - link_stroke_width )
376+ . attr ( "refY" , 0 )
377+ . attr ( "markerWidth" , arrowhead_size * 2 )
378+ . attr ( "markerHeight" , arrowhead_size * 2 )
379+ . attr ( "orient" , "auto" ) ;
380+
381+ m . append ( "path" )
382+ . attr ( "d" , `M 0 -${ arrowhead_size } L ${ arrowhead_size * 2 } 0 L 0 ${ arrowhead_size } ` )
383+ . attr ( "fill" , fillColor ) ;
384+ }
311385
312386 return
313387 }
0 commit comments