Skip to content Skip to sidebar Skip to footer

Fade Links And Nodes That Are Not Immediately Connected To The Node Hovered On In A D3 Graph

I am now to d3 and web development in general. I am creating a graph using the d3 library. I am trying to ensure that whenever the user hovers upon a node, the opacity of its imme

Solution 1:

two issues to resolve

  1. For your nodes, as they are image files, you need set their 'opacity', and not the stroke/fill opacity.

    node.style("opacity", function(o) {
        thisOpacity = isConnected(d, o) ? 1 : opacity;
        return thisOpacity;
    });
    
  2. For your links, assuming the name attributes are unique, you should match the link's source and target to the chosen node's name.

    link.style("stroke-opacity", function(o) {
        return o.source.name === d.name || o.target.name === d.name ? 1 : opacity;
    });
    

Solution 2:

Addition to @TomShanley answer

Why are you using d3v3 if you are new to d3? Currently we are at d3v5 and the API has much been improved.

The program does not run out of the box because in determining linkedByIndex it complains that links does not exist. It should be json.links.

There is no need to put break after a return in linkColor.

You search for elements of class svg.selectAll(".nodes") but you create elements with .attr("class", "node"). This will not work if you want to use the enter-exit-update properly. The same with the links: search for class links but add elements with class lol.

Your markers are not unique and no need to use each to add the marker-end. Maybe best to create a set of markers based on color and just reference them. In the original code you have multiple tags with the same id. And an id in HTML should be unique.

// Build the linkvar link = svg.selectAll(".lol")
    .data(json.links)
    .enter().append("line")
    .attr("class", "lol")
    .style("stroke-width", "2")
    .attr("stroke", function(d){
        returnlinkColor(d.colorCode);})
    .attr("marker-end", function(d, i){
        returnmarker(i, linkColor(d.colorCode));} );

functionmarker(i, color) {
    var markId = "#marker"+i;
    svg.append("svg:marker")
        .attr("id", markId.replace("#", ""))
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 10)
        .attr("refY", 0)
        .attr("markerWidth", 15)
        .attr("markerHeight", 15)
        .attr("orient", "auto")
        .attr("markerUnits", "userSpaceOnUse")
        .append("svg:path")
        .attr("d", "M0,-5L10,0L0,5")
        .style("fill", color);

    return"url(" + markId + ")";
};

Edit Unique markers, link is path from edge to edge

I have modified the code to have:

  • unique markers for each color put in the defs tag of the svg. Create a new marker if not already done for this color using an object to keep track.
  • links are now paths to apply a marker trick described by Gerardo
  • images are now centered on the node position, this only works for circular images.

Here is the full code

var width = 960,
    height = 500;

// initializationvar svg = d3.select("div").append("svg")
    .attr("width", width)
    .attr("height", height)
    .attr("id", "blueLine"); // the graph invisible thing :)var svgDefs = svg.append("defs");

var force = d3.layout.force()
    .gravity(0) // atom's cohesiveness / elasticity of imgs :)
    .distance(150) // how far the lines ---> arrows :)
    .charge(-50) // meta state transition excitement
    .linkDistance(140)
    //.friction(0.55) // similar to charge for quick reset :)
    .size([width, height]); // degree of freedom to the canvas// exception handling
d3.json("/fade-links.json", function(error, json) {
    if (error) throw error;

    var imageSize = { width:55, height:55 };

    // Restart the force layout
    force
        .nodes(json.nodes)
        .links(json.links)
        .start();

    var markersDone = {};

    // Build the linkvar link = svg.selectAll(".lol")
        .data(json.links)
        .enter().append("path")
        .attr("class", "lol")
        .style("stroke-width", "2")
        .attr("stroke", function(d){
            returnlinkColor(d.colorCode);})
        .attr("marker-end", function(d){
            returnmarker(linkColor(d.colorCode));} );

    functionmarker(color) {
        var markerId = markersDone[color];
        if (!markerId) {
            markerId = color;
            markersDone[color] = markerId;
            svgDefs.append("svg:marker")
                .attr("id", color.replace("#", ""))
                .attr("viewBox", "0 -5 10 10")
                .attr("refX", 10)
                .attr("refY", 0)
                .attr("markerWidth", 15)
                .attr("markerHeight", 15)
                .attr("orient", "auto")
                .attr("markerUnits", "userSpaceOnUse")
                .append("svg:path")
                .attr("d", "M0,-5L10,0L0,5")
                .style("fill", color);
        }
        return"url(" + markerId + ")";
    };

    // this link : https://stackoverflow.com/questions/32964457/match-arrowhead-color-to-line-color-in-d3// create a nodevar node = svg.selectAll(".node")
        .data(json.nodes)
        .enter().append("g")
        .attr("class", "node")
        .call(force.drag)
        .on("mouseover", fade(.2))
        .on("mouseout", fade(1));

    // Define the div for the tooltipvar div = d3.select("body").append("pre")
        .attr("class", "tooltip")
        .style("opacity", 0);

    // Append custom images
    node.append("svg:image")
        .attr("xlink:href",  function(d) { return d.img;}) // update the node with the image
        .attr("x", function(d) { return -imageSize.width*0.5;}) // how far is the image from the link??
        .attr("y", function(d) { return -imageSize.height*0.5;}) // --- same ---
        .attr("height", imageSize.width)
        .attr("width", imageSize.height);

    node.append("text")
        .attr("class", "labelText")
        .attr("x", function(d) { return0;})
        .attr("y", function(d) { return imageSize.height*0.75;})
        .text(function(d) { return d.name });

    force.on("tick", function() {
        // use trick described by Gerardo to only draw link from image border to border:  https://stackoverflow.com/q/51399062/9938317
        link.attr("d", function(d) {
            var dx = d.target.x - d.source.x,
                dy = d.target.y - d.source.y;
            var angle = Math.atan2(dy, dx);
            var radius = imageSize.width*0.5;
            var offsetX = radius * Math.cos(angle);
            var offsetY = radius * Math.sin(angle);
            return ( `M${d.source.x + offsetX},${d.source.y + offsetY}L${d.target.x - offsetX},${d.target.y - offsetY}`);
          });

        node.attr("transform", function(d) { return"translate(" + d.x + "," + d.y + ")"; });

        force.stop();
    });

    functionlinkColor(linkCode) {
        switch (linkCode)
        {
            case'ctoc': return'#0000FF';//bluecase'ctof': return'#00afaa';//greencase'ftoc': return'#fab800';//yellowcase'ftof': return'#7F007F';//purple
        }
        return'#0950D0';//generic blue
    }

    // build a dictionary of nodes that are linkedvar linkedByIndex = {};
    json.links.forEach(function(d) {
        linkedByIndex[d.source.id + "," + d.target.id] = 1;
    });

    // check the dictionary to see if nodes are linkedfunctionisConnected(a, b) {
        return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
    }

    // fade nodes on hoverfunctionfade(opacity) {
        returnfunction(d) {
            // check all other nodes to see if they're connected// to this one. if so, keep the opacity at 1, otherwise// fade
            node.style("opacity", function(o) {
                returnisConnected(d, o) ? 1 : opacity;
            });
            // also style link accordingly
            link.style("opacity", function(o) {
                return o.source.name === d.name || o.target.name === d.name ? 1 : opacity;
            });
        };
    }
});

Post a Comment for "Fade Links And Nodes That Are Not Immediately Connected To The Node Hovered On In A D3 Graph"