2

I have made a force directed graph which looks similar to this.note this is a random example from the internet

I would like the Nine Inch Nails node, in the centre, to be an image but the rest of the nodes to just be circles. Following this answer it seemed not too difficult but I just can't get my head around the combonation of svg, defs, patterns and d3.

My code is:

      var simulation =
      d3.forceSimulation()
      .force("charge", d3.forceManyBody().strength(-50))
      .force("collide", d3.forceCollide().radius(function (d) { return 15 - d.group}).strength(2).iterations(2))
      .force("link", d3.forceLink().id(function(d, i) { return i;}).distance(20).strength(0.9))
      .force("center", d3.forceCenter(width/2, height/2))
      .force('X', d3.forceX(width/2).strength(0.15)) 
      .force('Y', d3.forceY(height/2).strength(0.15));

  var link = svg.append("g")
    .attr("class", "links")
    .selectAll("line")
    .data(graph.links)
    .enter().append("line")

    var defs = svg.append('svg:defs');

    defs.append("svg:pattern")
          .attr("id", "vit-icon")
          .attr("width", 48)
          .attr("height", 48)
          .attr("patternUnits", "userSpaceOnUse")
          .append("svg:image")
          .attr("xlink:href", "http://placekitten.com/g/48/48")
          .attr("width", 48)
          .attr("height", 48)
          .attr("x", width/2)
          .attr("y", height/2)

  var node = svg.append("g")
    .attr("class", "nodes")
    .selectAll("circle")
    .data(graph.nodes)
    .enter().append("circle")
      .attr("id", function(d, i) { return 'c'+i})
      .attr("r", radius)
      .attr("fill", function(d) {
            if(d.group==0) {return "url(#vit-icon)";}
            else {return color(d.group); }
          })
    .call(d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended));

As I say, it seems straight forward in my mind. Basically what I think I'm trying to do is have the image in the svg pattern, then use an if statement to tell my root node to use the image url rather than fill with colour.

In dev tools I can see inspect the image and it shows the blue area it would take up and the node I want it to be associatated to has the 'url(#vit-icon)' as it's fill attribute. But it is not showing the image or any fill for that node.

What am I doing wrong? Or is this the complete wrong approach? Thanks.

Community
  • 1
  • 1
damtypo
  • 150
  • 1
  • 18

1 Answers1

1

In your defs, just change:

.attr("x", width/2)
.attr("y", height/2)

To:

.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", "vit-icon")
    .attr("width", 1)
    .attr("height", 1)
    .append("svg:image")
    .attr("xlink:href", "http://66.media.tumblr.com/avatar_1c725152c551_128.png")
    .attr("width", 48)
    .attr("height", 48)
    .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", d=> d.id === 1 ? 24 : 14)
    .style("fill", function(d) {
        if (d.id === 1) {
            return "url(#vit-icon)";
        } else {
            return "teal"
        }
    })

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>
Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
  • 1
    PS: NIN *is* Trent Reznor. – Gerardo Furtado Nov 29 '16 at 03:34
  • Thank you, this is nearly working for me. Could you explain why the removal of the "patternUnits", "userSpaceOnUse", as well as changing the first width and height values in defs to 1? I am having trouble scaling and centring the image to the node. – damtypo Nov 29 '16 at 04:26
  • I just removed those to make the snippet smaller. The important thing here is changing the `x` and `y` positions. – Gerardo Furtado Nov 29 '16 at 04:51
  • OK, I think I understand how it works. For anyone like me reading this in the future, to make the image fit nice inside the node, make the width/height I referenced in the previous comment, twice the size of the node radius you want it fit into to. In Geraldo's code that is 48 and 24. Oh and I definitely agree about Trent Reznor being NiN... that's just a random example graph I found:) – damtypo Nov 29 '16 at 05:16