Search code examples
javascriptd3.jsd3-force-directed

d3.js Force Directed Graph - Using Images instead of Circles for the Nodes


I am trying to visualize a dynamic network topology by using d3.js. So far, I could make it work by putting circles as nodes, but I have to put different custom images for different node types.

My current code is like this:

 node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(jsonTry.nodes)
.enter().append("circle")
    .attr("r", 10)
    .attr("fill", function(d) { return color(d.group); })
    .call(d3.drag()
        .subject(dragsubject)
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

As a start, I wanted to put an image to all nodes to see how it works like the following:

node = svg.append("g")
.attr("class", "nodes")
.data(graph.nodes)

node.append("svg:image")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", -8)
.attr("y", -8)
.attr("width", 16)
.attr("height", 16);

It doesn't seem to be working this way since I can see just the links, and nodes just dissappeared. I found some examples using d3 v3, but currently I'm using d3 v4 and want to implement it accordingly (since I had problems with forceLink() not being included in v3)

Thanks in advance.


Solution

  • Your approach would work if you were appending the <image> to the <g>. However, you're appending the <image> to the <circle> element, and that won't work.

    You have two solutions:

    1. Append the image to the groups, not to the circles;
    2. Keep your circles, but use a def:

      var defs = svg.append('svg:defs');
      
      defs.append("svg:pattern")
          .attr("id", "myPattern")
          .attr("width", 1)
          .attr("height", 1)
          .append("svg:image")
          .attr("xlink:href", "https://github.com/favicon.ico")
          .attr("width", 16)
          .attr("height", 16)
          .attr("x", 0)
          .attr("y", 0);
      

    Here is a demo:

    var nodes = [{
        "id": 1,
    }, {
        "id": 2,
    }, {
        "id": 3,
    }, {
        "id": 4,
    }, {
        "id": 5,
    }, {
        "id": 6,
    }, {
        "id": 7,
    }, {
        "id": 8,
    }];
    
    var links = [{
        source: 1,
        target: 2
    }, {
        source: 1,
        target: 3
    }, {
        source: 1,
        target: 4
    }, {
        source: 2,
        target: 5
    }, {
        source: 2,
        target: 6
    }, {
        source: 1,
        target: 7
    }, {
        source: 7,
        target: 8
    }];
    
    var index = 10;
    var svg = d3.select("svg"),
        width = +svg.attr("width"),
        height = +svg.attr("height"),
        node,
        link;
    
    var defs = svg.append('svg:defs');
    
    defs.append("svg:pattern")
        .attr("id", "myPattern")
        .attr("width", 1)
        .attr("height", 1)
        .append("svg:image")
        .attr("xlink:href", "https://github.com/favicon.ico")
        .attr("width", 16)
        .attr("height", 16)
        .attr("x", 0)
        .attr("y", 0);
    
    var simulation = d3.forceSimulation()
        .force("link", d3.forceLink().id(function(d) {
            return d.id;
        }).distance(100))
        .force("collide", d3.forceCollide(50))
        .force("charge", d3.forceManyBody())
        .force("center", d3.forceCenter(width / 2, height / 2));
    
    link = svg.selectAll(".link")
        .data(links, function(d) {
            return d.target.id;
        })
    
    link = link.enter()
        .append("line")
        .attr("class", "link");
    
    node = svg.selectAll(".node")
        .data(nodes, function(d) {
            return d.id;
        })
    
    node = node.enter()
        .append("g")
        .attr("class", "node")
        .call(d3.drag()
            .on("start", dragstarted)
            .on("drag", dragged)
            .on("end", dragended));
    
    node.append("circle")
        .attr("r", 10)
        .style("fill", "url(#myPattern)")
    
    simulation
        .nodes(nodes)
        .on("tick", ticked);
    
    simulation.force("link")
        .links(links);
    
    
    function ticked() {
        link
            .attr("x1", function(d) {
                return d.source.x;
            })
            .attr("y1", function(d) {
                return d.source.y;
            })
            .attr("x2", function(d) {
                return d.target.x;
            })
            .attr("y2", function(d) {
                return d.target.y;
            });
    
        node
            .attr("transform", function(d) {
                return "translate(" + d.x + ", " + d.y + ")";
            });
    }
    
    function dragstarted(d) {
        if (!d3.event.active) simulation.alphaTarget(0.3).restart()
    }
    
    function dragged(d) {
        d.fx = d3.event.x;
        d.fy = d3.event.y;
    }
    
    function dragended(d) {
        if (!d3.event.active) simulation.alphaTarget(0);
        d.fx = undefined;
        d.fy = undefined;
    }
    .link {
      stroke: #aaa;
    }
    
    .node {
      pointer-events: all;
      stroke: none;
      stroke-width: 40px;
    }
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <svg width="500" height="300"></svg>