可折叠力导向图上的D3.js标题

时间:2014-11-05 00:22:42

标签: javascript jquery d3.js

所以我一直在研究基于以下example的可折叠力量导向图。

我试图从中推进并为每个节点添加标题。我在stackoverflow上跟踪了类似的answer,但是我无法将该答案的解决方案实现到上面的示例和其他类似的解决方案中。

请有人指出我正确的方向。

在实施前查看下面的项目代码。

enter image description here

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

2 个答案:

答案 0 :(得分:2)

有趣的是,它只是在你的代码中作为代码片段工作,但只有在你折叠或扩展节点之后。这应该可以让您了解问题所在。基本上,您在节点实际存在之前将title元素添加到每个节点。移动

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

之后
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);

呼叫。

以下是完成该更改的代码段:

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.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="viz"></div>

注意:在展开或折叠节点时,此更正存在问题,标题设置不正确。这是因为d3将数据绑定到DOM元素的方式。默认情况下,d3将基于索引进行绑定。因为添加和删除数据可能会改变顺序,所以数据的索引值可能会发生变化,这意味着DOM元素和数据之间的关系会被破坏。这可以通过显式设置数据绑定的密钥来纠正。您可以使用任何唯一的键,但对于此示例,我刚刚使用了您定义的name属性。像这样:

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.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="viz"></div>

这里的区别是node.data电话:

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

.data(nodes, function...)调用中的函数指示d3使用从函数返回的任何内容作为键(有关详细信息,请参阅https://github.com/mbostock/d3/wiki/Selections#data)。这将确保在折叠和扩展节点时删除,添加和更新正确的图形元素。

答案 1 :(得分:2)

像这样向节点添加标题。

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();

请注意,标题应附加在圆形节点之后。否则标题可能会被切断。

同时在刻度函数中更新标题的位置。

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

以下是jsfiddle