D3 force directed graph, different shape according to data and value given?

dzordz picture dzordz · Aug 29, 2013 · Viewed 9.4k times · Source

I've made a force directed graph and I wanted to change shape of nodes for data which contains "entity":"company" so they would have rectangle shape, and other one without this part of data would be circles as they are now.

You can see my working example with only circle nodes here: http://jsfiddle.net/dzorz/uWtSk/

I've tried to add rectangles with if else statement in part of code where I append shape to node like this:

function(d)
    {
        if (d.entity == "company")
        {
            node.append("rect")
                .attr("class", function(d){ return "node type"+d.type})
                .attr("width", 100)
                .attr("height", 50)
                .call(force.drag);
        }
        else
        {
        node.append("circle")
            .attr("class", function(d){ return "node type"+d.type})
            .attr("r", function(d) { return radius(d.value) || 10 })
            //.style("fill", function(d) { return fill(d.type); })
            .call(force.drag);
        }
    }

But then I did not get any shape at all on any node.

What Is a proper way to set up this?

The whole code looks like this: script:

var data = {"nodes":[
                        {"name":"Action 4", "type":5, "slug": "", "value":265000},
                        {"name":"Action 5", "type":6, "slug": "", "value":23000},
                        {"name":"Action 3", "type":4, "slug": "", "value":115000},
                        {"name":"Yahoo", "type":1, "slug": "www.yahoo.com", "entity":"company"},
                        {"name":"Google", "type":1, "slug": "www.google.com", "entity":"company"},
                        {"name":"Action 1", "type":2, "slug": "",},
                        {"name":"Action 2", "type":3, "slug": "",},
                        {"name":"Bing", "type":1, "slug": "www.bing.com", "entity":"company"},
                        {"name":"Yandex", "type":1, "slug": "www.yandex.com)", "entity":"company"}
                    ], 
            "links":[
                        {"source":0,"target":3,"value":10},
                        {"source":4,"target":3,"value":1},
                        {"source":1,"target":7,"value":10},
                        {"source":2,"target":4,"value":10},
                        {"source":4,"target":7,"value":1},
                        {"source":4,"target":5,"value":10},
                        {"source":4,"target":6,"value":10},
                        {"source":8,"target":4,"value":1}
                        ]
               }    



    var w = 560,
        h = 500,
        radius = d3.scale.log().domain([0, 312000]).range(["10", "50"]);

    var vis = d3.select("body").append("svg:svg")
        .attr("width", w)
        .attr("height", h);

        vis.append("defs").append("marker")
        .attr("id", "arrowhead")
        .attr("refX", 17 + 3) /*must be smarter way to calculate shift*/
        .attr("refY", 2)
        .attr("markerWidth", 6)
        .attr("markerHeight", 4)
        .attr("orient", "auto")
        .append("path")
            .attr("d", "M 0,0 V 4 L6,2 Z"); //this is actual shape for arrowhead

    //d3.json(data, function(json) {
        var force = self.force = d3.layout.force()
            .nodes(data.nodes)
            .links(data.links)
            .distance(100)
            .charge(-1000)
            .size([w, h])
            .start();



        var link = vis.selectAll("line.link")
            .data(data.links)
            .enter().append("svg:line")
            .attr("class", function (d) { return "link" + d.value +""; })
            .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; })
            .attr("marker-end", function(d) {
                                                if (d.value == 1) {return "url(#arrowhead)"}
                                                else    { return " " }
                                            ;});


        function openLink() {
        return function(d) {
            var url = "";
            if(d.slug != "") {
                url = d.slug
            } //else if(d.type == 2) {
                //url = "clients/" + d.slug
            //} else if(d.type == 3) {
                //url = "agencies/" + d.slug
            //}
            window.open("//"+url)
        }
    }




        var node = vis.selectAll("g.node")
            .data(data.nodes)
          .enter().append("svg:g")
            .attr("class", "node")
            .call(force.drag);

        node.append("circle")
          .attr("class", function(d){ return "node type"+d.type})
            .attr("r", function(d) { return radius(d.value) || 10 })
          //.style("fill", function(d) { return fill(d.type); })
          .call(force.drag);

        node.append("svg:image")
            .attr("class", "circle")
            .attr("xlink:href", function(d){ return d.img_href})
            .attr("x", "-16px")
            .attr("y", "-16px")
            .attr("width", "32px")
            .attr("height", "32px")
            .on("click", openLink());

        node.append("svg:text")
            .attr("class", "nodetext")
            .attr("dx", 0)
            .attr("dy", ".35em")
            .attr("text-anchor", "middle")
            .text(function(d) { return d.name });

        force.on("tick", function() {
          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 + ")"; });
        });
    //});    

css:

.link10 { stroke: #ccc; stroke-width: 3px; stroke-dasharray: 3, 3; }
.link1 { stroke: #000; stroke-width: 3px;}
.nodetext { pointer-events: none; font: 10px sans-serif; }

.node.type1 {
  fill:brown;
}
.node.type2 {
  fill:#337147;
}
.node.type3 {
  fill:blue;
}
.node.type4 {
  fill:red;
}

.node.type5 {
    fill:#1BC9E0;
}

.node.type6 {
    fill:#E01B98;
}

image.circle {
    cursor:pointer;
}

You can edit my jsfiddle linked on beginning of post...

Answer

bu11d0zer picture bu11d0zer · Jul 17, 2014

Solution here: http://jsfiddle.net/Bull/4btFx/1/

I got this to work by adding a class to each node, then using "selectAll" for each class to add the shapes. In the code below, I'm adding a class "node" and a class returned by my JSON (d.type) which is either "rect" or "ellipse".

  var node = container.append("g")
  .attr("class", "nodes")
  .selectAll(".node")
  .data(graph.nodes)
  .enter().append("g")
  .attr("class", function(d) {
     return d.type + " node";
  })
  .call(drag);

Then you can add the shape for all elements of each class:

  d3.selectAll(".rect").append("rect")
  .attr("width", window.nodeWidth)
  .attr("height", window.nodeHeight)
  .attr("class", function(d) { 
     return "color_" + d.class 
  });

  d3.selectAll(".ellipse").append("rect")
  .attr("rx", window.nodeWidth*0.5)
  .attr("ry", window.nodeHeight*0.5)
  .attr("width", window.nodeWidth)
  .attr("height", window.nodeHeight)
  .attr("class", function(d) { 
     return "color_" + d.class 
  });

In the above example, I used rectangles with radius to draw the ellipses since it centers them the same way as the rectangles. But it works with other shapes too. In the jsfiddle I linked, the centering is off, but the shapes are right.