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

≡放荡痞女 提交于 2019-11-29 18:18:36

问题


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...


回答1:


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.




回答2:


I implemented this behavior using the filter method that I gleaned from Filtering in d3.js on bl.ocks.org.

initGraphNodeShapes() {
  let t = this;

  let graphNodeCircles =
    t.graphNodesEnter
      .filter(d => d.shape === "circle")
      .append("circle")
      .attr("r", 15)
      .attr("fill", "green");

  let graphNodeRects =
    t.graphNodesEnter
      .filter(d => d.shape === "rect")
      .append("rect")
      .attr("width", 20)
      .attr("height", 10)
      .attr("x", -10) // -1/2 * width
      .attr("y", -5) // -1/2 * height
      .attr("fill", "blue");

  return graphNodeCircles.merge(graphNodeRects);
}

I have this inside of initGraphNodeShapes call because my code is relatively large and refactored. The t.graphNodesEnter is a reference to the data selection after the data join enter() call elsewhere. Ping me if you need more context. Also, I use the d => ... version because I'm using ES6 which enables lambdas. If you're using pre-ES6, then you'll have to change it to the function(d)... form.




回答3:


I am one step ahead of you :)

I resolved your problem with using "path" instead of "circle" or "rect", you can look my solution and maybe help me to fix problem which I have...

D3 force-directed graph: update node position



来源:https://stackoverflow.com/questions/18509792/d3-force-directed-graph-different-shape-according-to-data-and-value-given

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!