最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - D3.js Titles on Collapsible Force-Directed graph - Stack Overflow

programmeradmin5浏览0评论

So i've been working on a collapsible force-directed graph based of the following example.

Im trying to advance from that and add titles to each node. I've followed a similar answer here on stackoverflow, but I cannot implement the solution from that answer into the example above and other similar solutions.

Please can someone point me in the correct direction.

See project code below before implementation.

JS

var w = 600,
    h = 600,
    radius = 10,
    node,
    link,
    root;

var force = d3.layout.force()
    .on("tick", tick)
    .charge(function(d) { return -500; })
    .linkDistance(function(d) { return d.target._children ? 100 : 50; })
    .size([w, h - 160]);

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

root = words[0]; //set root node
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();

function update() {
    var nodes = flatten(root),
    links = d3.layout.tree().links(nodes);

    // Restart the force layout.
    force
        .nodes(nodes)
        .links(links)
        .start();

    // Update the links…
    link = svg.selectAll(".link")
        .data(links);

    // Enter any new links.
    link.enter().insert("svg:line", ".node")
        .attr("class", "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; });

    // Exit any old links.
    link.exit().remove();

    // Update the nodes…
    node = svg.selectAll("circle.node")
        .data(nodes)
        .style("fill", color);

    node.transition()
        .attr("r", radius);

    node.append("title")
        .text(function(d) { return d.name; });

    // Enter any new nodes.
    node.enter().append("svg:circle")
        .attr("class", "node")
        .attr("cx", function(d) { return d.x; })
        .attr("cy", function(d) { return d.y; })
        .attr("r", radius)
        .style("fill", color)
        .on("click", click)
        .call(force.drag);

    // Exit any old nodes.
    node.exit().remove();
}

function tick() {
    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("cx", function(d) { return d.x; })
          .attr("cy", function(d) { return d.y; });
}

// Color leaf nodes orange, and packages white or blue.
function color(d) {
    if(d._children){
        return "#95a5a6";
    }else{
        switch(d.group) {
            case 'r': //adverb
                return "#e74c3c";
                break;
            case 'n': //noun
                return "#3498db";
                break;
            case 'v': //verb
                return "#2ecc71";
                break;
            case 's': //adjective
                return "#e78229";
                break;
            default:
                return "#9b59b6";
        }
    }
}

// Toggle children on click.
function click(d) {
    if (d.children) {
        d._children = d.children;
        d.children = null;
    } else {
        d.children = d._children;
        d._children = null;
    }
    update();
}

// Returns a list of all nodes under the root.
function flatten(root) {
    var nodes = [], i = 0;

    function recurse(node) {
        if (node.children) node.size = node.children.reduce(function(p, v) { return p + recurse(v); }, 0);
        if (!node.id) node.id = ++i;
        nodes.push(node);
        return node.size;
    }

    root.size = recurse(root);
    return nodes;
}

CSS

circle.node {
    cursor: pointer;
    stroke: #34495e;
    stroke-width: 2px;
    box-sizing: border-box;
    stroke-location: inside;
}

line.link {
    fill: none;
    stroke: #34495e;
    stroke-width: 1.5px;
}

HTML

<!DOCTYPE html>
<body>
<script src=".v2.min.js?2.9.6"></script>
    <script>

        var words = [
    {
        "group":"n",
        "word":"main node",
        "children":[
            {
                "group":"n",
                "name":"sub node 1"
            },
            {
                "group":"n",
                "name":"sub node 2"
            },
            {
                "group":"n",
                "name":"sub node 3"
            },
            {
                "group":"v",
                "name":"sub node 4"
            },
            {
                "group":"s",
                "name":"sub node 5"
            },
            {
                "group":"s",
                "name":"sub node 6"
            },
            {
                "group":"s",
                "name":"sub node 7"
            },
            {
                "group":"s",
                "name":"sub node 8"
            },
            {
                "group":"s",
                "name":"sub node 9"
            },
            {
                "group":"s",
                "name":"sub node 10"
            },
            {
                "group":"s",
                "name":"sub node 11"
            },
            {
                "group":"r",
                "name":"sub node 12",
                "children":[
                    {
                        "group":"r",
                        "name":"sub sub node 1"
                    },
                    {
                        "group":"r",
                        "name":"sub sub node 2"
                    },
                    {
                        "group":"r",
                        "name":"sub sub node 3"
                    }
                ]
            }
        ]
    }
]
    </script>
</body>

JSFiddle

So i've been working on a collapsible force-directed graph based of the following example.

Im trying to advance from that and add titles to each node. I've followed a similar answer here on stackoverflow, but I cannot implement the solution from that answer into the example above and other similar solutions.

Please can someone point me in the correct direction.

See project code below before implementation.

JS

var w = 600,
    h = 600,
    radius = 10,
    node,
    link,
    root;

var force = d3.layout.force()
    .on("tick", tick)
    .charge(function(d) { return -500; })
    .linkDistance(function(d) { return d.target._children ? 100 : 50; })
    .size([w, h - 160]);

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

root = words[0]; //set root node
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();

function update() {
    var nodes = flatten(root),
    links = d3.layout.tree().links(nodes);

    // Restart the force layout.
    force
        .nodes(nodes)
        .links(links)
        .start();

    // Update the links…
    link = svg.selectAll(".link")
        .data(links);

    // Enter any new links.
    link.enter().insert("svg:line", ".node")
        .attr("class", "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; });

    // Exit any old links.
    link.exit().remove();

    // Update the nodes…
    node = svg.selectAll("circle.node")
        .data(nodes)
        .style("fill", color);

    node.transition()
        .attr("r", radius);

    node.append("title")
        .text(function(d) { return d.name; });

    // Enter any new nodes.
    node.enter().append("svg:circle")
        .attr("class", "node")
        .attr("cx", function(d) { return d.x; })
        .attr("cy", function(d) { return d.y; })
        .attr("r", radius)
        .style("fill", color)
        .on("click", click)
        .call(force.drag);

    // Exit any old nodes.
    node.exit().remove();
}

function tick() {
    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("cx", function(d) { return d.x; })
          .attr("cy", function(d) { return d.y; });
}

// Color leaf nodes orange, and packages white or blue.
function color(d) {
    if(d._children){
        return "#95a5a6";
    }else{
        switch(d.group) {
            case 'r': //adverb
                return "#e74c3c";
                break;
            case 'n': //noun
                return "#3498db";
                break;
            case 'v': //verb
                return "#2ecc71";
                break;
            case 's': //adjective
                return "#e78229";
                break;
            default:
                return "#9b59b6";
        }
    }
}

// Toggle children on click.
function click(d) {
    if (d.children) {
        d._children = d.children;
        d.children = null;
    } else {
        d.children = d._children;
        d._children = null;
    }
    update();
}

// Returns a list of all nodes under the root.
function flatten(root) {
    var nodes = [], i = 0;

    function recurse(node) {
        if (node.children) node.size = node.children.reduce(function(p, v) { return p + recurse(v); }, 0);
        if (!node.id) node.id = ++i;
        nodes.push(node);
        return node.size;
    }

    root.size = recurse(root);
    return nodes;
}

CSS

circle.node {
    cursor: pointer;
    stroke: #34495e;
    stroke-width: 2px;
    box-sizing: border-box;
    stroke-location: inside;
}

line.link {
    fill: none;
    stroke: #34495e;
    stroke-width: 1.5px;
}

HTML

<!DOCTYPE html>
<body>
<script src="http://d3js/d3.v2.min.js?2.9.6"></script>
    <script>

        var words = [
    {
        "group":"n",
        "word":"main node",
        "children":[
            {
                "group":"n",
                "name":"sub node 1"
            },
            {
                "group":"n",
                "name":"sub node 2"
            },
            {
                "group":"n",
                "name":"sub node 3"
            },
            {
                "group":"v",
                "name":"sub node 4"
            },
            {
                "group":"s",
                "name":"sub node 5"
            },
            {
                "group":"s",
                "name":"sub node 6"
            },
            {
                "group":"s",
                "name":"sub node 7"
            },
            {
                "group":"s",
                "name":"sub node 8"
            },
            {
                "group":"s",
                "name":"sub node 9"
            },
            {
                "group":"s",
                "name":"sub node 10"
            },
            {
                "group":"s",
                "name":"sub node 11"
            },
            {
                "group":"r",
                "name":"sub node 12",
                "children":[
                    {
                        "group":"r",
                        "name":"sub sub node 1"
                    },
                    {
                        "group":"r",
                        "name":"sub sub node 2"
                    },
                    {
                        "group":"r",
                        "name":"sub sub node 3"
                    }
                ]
            }
        ]
    }
]
    </script>
</body>

JSFiddle

Share Improve this question edited May 23, 2017 at 10:30 CommunityBot 11 silver badge asked Nov 5, 2014 at 0:22 Oliver EvansOliver Evans 9892 gold badges20 silver badges45 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 2

Add titles to node like this.

title = svg.selectAll("text.title")    
     .data(nodes);

title.enter()
    .append("text") //In your code you used title instead of text
    .attr("class", "title")
    .text(function(d) { return d.name; });

title.exit().remove();

Note that titles should be appended after circle nodes. Or else titles may cut off.

Also update the position of title in tick function.

 title.attr("transform", function(d){ return "translate("+d.x+","+d.y+")"; });

Here is the jsfiddle

Interestingly, it is kind of working as a snippet here with your code as is, but only after you've collapsed or expanded a node. This should give you some idea of where your problem is. Basically, you're adding the title element to each node before the nodes actually exist. Move the

node.append("title")
    .text(function(d) { return d.name; });

to after the

node.enter().append("svg:circle")
    .attr("class", "node")
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; })
    .attr("r", radius)
    .style("fill", color)
    .on("click", click)
    .call(force.drag);

call.

Here is a snippet with that change pleted:

var words = [{
  "group": "n",
  "word": "main node",
  "children": [{
    "group": "n",
    "name": "sub node 1"
  }, {
    "group": "n",
    "name": "sub node 2"
  }, {
    "group": "n",
    "name": "sub node 3"
  }, {
    "group": "v",
    "name": "sub node 4"
  }, {
    "group": "s",
    "name": "sub node 5"
  }, {
    "group": "s",
    "name": "sub node 6"
  }, {
    "group": "s",
    "name": "sub node 7"
  }, {
    "group": "s",
    "name": "sub node 8"
  }, {
    "group": "s",
    "name": "sub node 9"
  }, {
    "group": "s",
    "name": "sub node 10"
  }, {
    "group": "s",
    "name": "sub node 11"
  }, {
    "group": "r",
    "name": "sub node 12",
    "children": [{
      "group": "r",
      "name": "sub sub node 1"
    }, {
      "group": "r",
      "name": "sub sub node 2"
    }, {
      "group": "r",
      "name": "sub sub node 3"
    }]
  }]
}]


var w = 600,
  h = 600,
  radius = 10,
  node,
  link,
  root;

var force = d3.layout.force()
  .on("tick", tick)
  .charge(function(d) {
    return -500;
  })
  .linkDistance(function(d) {
    return d.target._children ? 100 : 50;
  })
  .size([w, h - 160]);

var svg = d3.select("#viz").append("svg")
  .attr("width", w)
  .attr("height", h);

root = words[0]; //set root node
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();

function update() {
  var nodes = flatten(root),
    links = d3.layout.tree().links(nodes);

  // Restart the force layout.
  force
    .nodes(nodes)
    .links(links)
    .start();

  // Update the links…
  link = svg.selectAll(".link")
    .data(links);

  // Enter any new links.
  link.enter().insert("svg:line", ".node")
    .attr("class", "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;
    });

  // Exit any old links.
  link.exit().remove();

  // Update the nodes…
  node = svg.selectAll("circle.node")
    .data(nodes)
    .style("fill", color);

  node.transition()
    .attr("r", radius);


  // Enter any new nodes.
  node.enter().append("svg:circle")
    .attr("class", "node")
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    })
    .attr("r", radius)
    .style("fill", color)
    .on("click", click)
    .call(force.drag);

  node.append("title")
    .text(function(d) {
      return d.name;
    });


  // Exit any old nodes.
  node.exit().remove();
}

function tick() {
  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("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    });
}

// Color leaf nodes orange, and packages white or blue.
function color(d) {
  if (d._children) {
    return "#95a5a6";
  } else {
    switch (d.group) {
      case 'r': //adverb
        return "#e74c3c";
        break;
      case 'n': //noun
        return "#3498db";
        break;
      case 'v': //verb
        return "#2ecc71";
        break;
      case 's': //adjective
        return "#e78229";
        break;
      default:
        return "#9b59b6";
    }
  }
}

// Toggle children on click.
function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update();
}

// Returns a list of all nodes under the root.
function flatten(root) {
  var nodes = [],
    i = 0;

  function recurse(node) {
    if (node.children) node.size = node.children.reduce(function(p, v) {
      return p + recurse(v);
    }, 0);
    if (!node.id) node.id = ++i;
    nodes.push(node);
    return node.size;
  }

  root.size = recurse(root);
  return nodes;
}
circle.node {
  cursor: pointer;
  stroke: #34495e;
  stroke-width: 2px;
  box-sizing: border-box;
  stroke-location: inside;
}
line.link {
  fill: none;
  stroke: #34495e;
  stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare./ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="viz"></div>

Note: This correction has an issue when expanding or collapsing a node, with the title being incorrectly set. This is because of the way d3 is binding the data to the DOM elements. By default d3 will bind based on index. Because adding and remove data can potentially change the order, the index values for the data could change, meaning that the relationship between DOM elements and data is broken. This can be corrected by explicitly setting the key for the binding of the data. You could use anything unique for the key, but for this example, I've just used the name property you've defined. Like so:

var words = [{
  "group": "n",
  "word": "main node",
  "children": [{
    "group": "n",
    "name": "sub node 1"
  }, {
    "group": "n",
    "name": "sub node 2"
  }, {
    "group": "n",
    "name": "sub node 3"
  }, {
    "group": "v",
    "name": "sub node 4"
  }, {
    "group": "s",
    "name": "sub node 5"
  }, {
    "group": "s",
    "name": "sub node 6"
  }, {
    "group": "s",
    "name": "sub node 7"
  }, {
    "group": "s",
    "name": "sub node 8"
  }, {
    "group": "s",
    "name": "sub node 9"
  }, {
    "group": "s",
    "name": "sub node 10"
  }, {
    "group": "s",
    "name": "sub node 11"
  }, {
    "group": "r",
    "name": "sub node 12",
    "children": [{
      "group": "r",
      "name": "sub sub node 1"
    }, {
      "group": "r",
      "name": "sub sub node 2"
    }, {
      "group": "r",
      "name": "sub sub node 3"
    }]
  }]
}]


var w = 600,
  h = 600,
  radius = 10,
  node,
  link,
  root;

var force = d3.layout.force()
  .on("tick", tick)
  .charge(function(d) {
    return -500;
  })
  .linkDistance(function(d) {
    return d.target._children ? 100 : 50;
  })
  .size([w, h - 160]);

var svg = d3.select("#viz").append("svg")
  .attr("width", w)
  .attr("height", h);

root = words[0]; //set root node
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();

function update() {
  var nodes = flatten(root),
    links = d3.layout.tree().links(nodes);

  // Restart the force layout.
  force
    .nodes(nodes)
    .links(links)
    .start();

  // Update the links…
  link = svg.selectAll(".link")
    .data(links);

  // Enter any new links.
  link.enter().insert("svg:line", ".node")
    .attr("class", "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;
    });

  // Exit any old links.
  link.exit().remove();

  // Update the nodes…
  node = svg.selectAll("circle.node")
    .data(nodes, function(d) {
      return d.name;
    })
    .style("fill", color);

  node.transition()
    .attr("r", radius);


  // Enter any new nodes.
  node.enter().append("svg:circle")
    .attr("class", "node")
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    })
    .attr("r", radius)
    .style("fill", color)
    .on("click", click)
    .call(force.drag);

  node.append("title")
    .text(function(d) {
      return d.name;
    });


  // Exit any old nodes.
  node.exit().remove();
}

function tick() {
  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("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    });
}

// Color leaf nodes orange, and packages white or blue.
function color(d) {
  if (d._children) {
    return "#95a5a6";
  } else {
    switch (d.group) {
      case 'r': //adverb
        return "#e74c3c";
        break;
      case 'n': //noun
        return "#3498db";
        break;
      case 'v': //verb
        return "#2ecc71";
        break;
      case 's': //adjective
        return "#e78229";
        break;
      default:
        return "#9b59b6";
    }
  }
}

// Toggle children on click.
function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update();
}

// Returns a list of all nodes under the root.
function flatten(root) {
  var nodes = [],
    i = 0;

  function recurse(node) {
    if (node.children) node.size = node.children.reduce(function(p, v) {
      return p + recurse(v);
    }, 0);
    if (!node.id) node.id = ++i;
    nodes.push(node);
    return node.size;
  }

  root.size = recurse(root);
  return nodes;
}
circle.node {

  cursor: pointer;

  stroke: #34495e;

  stroke-width: 2px;

  box-sizing: border-box;

  stroke-location: inside;

}

line.link {

  fill: none;

  stroke: #34495e;

  stroke-width: 1.5px;

}
<script src="https://cdnjs.cloudflare./ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="viz"></div>

The difference here is the node.data call:

  node = svg.selectAll("circle.node")
            .data(nodes, function(d) {
                return d.name;
            })
            .style("fill", color);

The function in the .data(nodes, function...) call instructs d3 to use whatever is returned from the function as the key (see https://github./mbostock/d3/wiki/Selections#data for more info). This will ensure that the correct graphical elements are removed, added and updated when collapsing and expanding nodes.

发布评论

评论列表(0)

  1. 暂无评论