score:1

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 link
var link = svg.selectAll(".lol")
    .data(json.links)
    .enter().append("line")
    .attr("class", "lol")
    .style("stroke-width", "2")
    .attr("stroke", function(d){
        return linkColor(d.colorCode);})
    .attr("marker-end", function(d, i){
        return marker(i, linkColor(d.colorCode));} );

function marker(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;

// initialization
var 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 link
    var link = svg.selectAll(".lol")
        .data(json.links)
        .enter().append("path")
        .attr("class", "lol")
        .style("stroke-width", "2")
        .attr("stroke", function(d){
            return linkColor(d.colorCode);})
        .attr("marker-end", function(d){
            return marker(linkColor(d.colorCode));} );

    function marker(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 node
    var 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 tooltip
    var 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) { return 0;})
        .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();
    });

    function linkColor(linkCode) {
        switch (linkCode)
        {
            case 'ctoc': return '#0000FF';//blue
            case 'ctof': return '#00afaa';//green
            case 'ftoc': return '#fab800';//yellow
            case 'ftof': return '#7F007F';//purple
        }
        return '#0950D0';//generic blue
    }

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

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

    // fade nodes on hover
    function fade(opacity) {
        return function(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) {
                return isConnected(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;
            });
        };
    }
});

score:2

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;
    });
    

Related Query

More Query from same tag