带标签的边界力图 d3.js

Bounded force graph with labels d3.js

我正在尝试创建带有标签的边界力图。 This is an official example of a bounded force graph without labels. Once, I add the labels I need to use a different tick function. When I do use the appropriate tick function the circle nodes indeed stay within the boundaries, however, the lines keep going if dragged beyond the boundary which is not the case for the example I linked. Here is the jsfiddle 我的带标签的有界图,但如果将其拖出边界,线条仍然会继续(您可以将整个图拖走,节点除外)。下面是代码。如果您能帮我弄清楚如何将线条保持在边界内并模仿我链接的示例的行为,那就太好了。谢谢。

js

var width = 280
height = 370
radius = 6;

var force = d3.layout.force()
    .nodes(d3.values(nodes))
    .links(links)
    .size([width, height])
    .linkDistance(50)
    .charge(-200)
    .on("tick", tick)
    .start();

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

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

var node = svg.selectAll(".node")
    .data(force.nodes())
    .enter().append("g")
    .attr("class", "node")
    .on("mouseover", mouseover)
    .on("mouseout", mouseout)
    .on("click", click)
    .on("dblclick", dblclick)
    .call(force.drag);

node.append("circle")
    .attr("r", radius)
    .style("fill", "#C71585");

node.append("text")
    .attr("x", 14)
    .attr("dy", ".35em")
    .style("fill", "#333")
    .text(function (d) {
    return d.name;
});

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

    var dx = function(d) {return Math.max(radius, Math.min(width - radius, d.x))}
    var dy = function(d) {return Math.max(radius, Math.min(width - radius, d.y))}

    node.attr("transform", function (d) {
        return "translate(" + dx(d) + "," + dy(d) + ")";
    });
}

function mouseover() {
    d3.select(this).select("circle").transition()
        .duration(750)
        .attr("r", 16);
}

function mouseout() {
    d3.select(this).select("circle").transition()
        .duration(750)
        .attr("r", 12);
}
// action to take on mouse click
function click() {
    d3.select(this).select("text").transition()
        .duration(750)
        .attr("x", 22)
        .style("stroke-width", ".5px")
        .style("opacity", 1)
        .style("fill", "#E34A33")
        .style("font", "17.5px serif");
    d3.select(this).select("circle").transition()
        .duration(750)
        .style("fill", "#E34A33")
        .attr("r", 16)
}

// action to take on mouse double click
function dblclick() {
    d3.select(this).select("circle").transition()
        .duration(750)
        .attr("r", 12)
        .style("fill", "#E34A33");
    d3.select(this).select("text").transition()
        .duration(750)
        .attr("x", 14)
        .style("stroke", "none")
        .style("fill", "#E34A33")
        .style("stroke", "none")
        .style("opacity", 0.6)
        .style("font", "14px serif");
}

var links = [{
  "source": "Analytics",
  "target": "Science"
}, {
  "source": "Analytics",
  "target": "Software"
}, {
  "source": "Analytics",
  "target": "Story"
}, {
  "source": "Science",
  "target": "Math"
}, {
  "source": "Science",
  "target": "Statistics"
}, {
  "source": "Software",
  "target": "R"
}, {
  "source": "Software",
  "target": "SAS"
}, {
  "source": "Software",
  "target": "Other"
}, {
  "source": "Story",
  "target": "Business Communication"
}, {
  "source": "Story",
  "target": "Visualization"
}];
var nodes = {}

// Compute the distinct nodes from the links.
links.forEach(function(link) {
  link.source = nodes[link.source] || (nodes[link.source] = {
    name: link.source
  });
  link.target = nodes[link.target] || (nodes[link.target] = {
    name: link.target
  });
  link.value = +link.value;
});

var width = 280
height = 370
radius = 6;

var force = d3.layout.force()
  .nodes(d3.values(nodes))
  .links(links)
  .size([width, height])
  .linkDistance(50)
  .charge(-200)
  .on("tick", tick)
  .start();

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

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

var node = svg.selectAll(".node")
  .data(force.nodes())
  .enter().append("g")
  .attr("class", "node")
  .on("mouseover", mouseover)
  .on("mouseout", mouseout)
  .on("click", click)
  .on("dblclick", dblclick)
  .call(force.drag);

node.append("circle")
  .attr("r", radius)
  .style("fill", "#C71585");

node.append("text")
  .attr("x", 14)
  .attr("dy", ".35em")
  .style("fill", "#333")
  .text(function(d) {
    return d.name;
  });


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

  var dx = function(d) {
    return Math.max(radius, Math.min(width - radius, d.x))
  }
  var dy = function(d) {
    return Math.max(radius, Math.min(width - radius, d.y))
  }

  node.attr("transform", function(d) {
    return "translate(" + dx(d) + "," + dy(d) + ")";
  });
}

function mouseover() {
  d3.select(this).select("circle").transition()
    .duration(750)
    .attr("r", 16);
}

function mouseout() {
    d3.select(this).select("circle").transition()
      .duration(750)
      .attr("r", 12);
  }
  // action to take on mouse click

function click() {
  d3.select(this).select("text").transition()
    .duration(750)
    .attr("x", 22)
    .style("stroke-width", ".5px")
    .style("opacity", 1)
    .style("fill", "#E34A33")
    .style("font", "17.5px serif");
  d3.select(this).select("circle").transition()
    .duration(750)
    .style("fill", "#E34A33")
    .attr("r", 16)
}

// action to take on mouse double click
function dblclick() {
  d3.select(this).select("circle").transition()
    .duration(750)
    .attr("r", 12)
    .style("fill", "#E34A33");
  d3.select(this).select("text").transition()
    .duration(750)
    .attr("x", 14)
    .style("stroke", "none")
    .style("fill", "#E34A33")
    .style("stroke", "none")
    .style("opacity", 0.6)
    .style("font", "14px serif");
}
.link {
  stroke: #666;
  opacity: 0.6;
  stroke-width: 1.5px;
}
.node circle {
  stroke: #fff;
  opacity: 0.6;
  stroke-width: 1.5px;
}
text {
  font: 15px serif;
  opacity: 0.6;
  pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

如果要为拖动创建边界,则可以重新定义拖动方法而不是刻度。

创建新拖动:

var drag = force.drag()
  .on("drag", dragmove);

在节点上使用此拖动。

并在新的dragmove方法中对节点的x,y坐标进行约束:

function dragmove(d) {
    var dx = function(d) {return Math.max(radius, Math.min(width - radius, d.x))}
    var dy = function(d) {return Math.max(radius, Math.min(width - radius, d.y))}
    d.px = Math.min(d3.event.x,200);
    d.py = Math.min(d3.event.y, 200);
    d.x = Math.min(d3.event.x, 200);
    d.y = Math.min(d3.event.y, 200);
}

您可以在这里查看: https://jsfiddle.net/n58to0tn/10/

刻度功能需要稍作改动。使用节点变换函数内的计算边界值更新 d.x 和 d.y 位置,然后更新 link 位置属性。

我把svg的背景颜色改了,为了看清楚装订效果

var links = [{
  "source": "Analytics",
  "target": "Science"
}, {
  "source": "Analytics",
  "target": "Software"
}, {
  "source": "Analytics",
  "target": "Story"
}, {
  "source": "Science",
  "target": "Math"
}, {
  "source": "Science",
  "target": "Statistics"
}, {
  "source": "Software",
  "target": "R"
}, {
  "source": "Software",
  "target": "SAS"
}, {
  "source": "Software",
  "target": "Other"
}, {
  "source": "Story",
  "target": "Business Communication"
}, {
  "source": "Story",
  "target": "Visualization"
}];
var nodes = {}

// Compute the distinct nodes from the links.
links.forEach(function(link) {
  link.source = nodes[link.source] || (nodes[link.source] = {
    name: link.source
  });
  link.target = nodes[link.target] || (nodes[link.target] = {
    name: link.target
  });
  link.value = +link.value;
});

var width = 280
height = 370
radius = 6;

var force = d3.layout.force()
  .nodes(d3.values(nodes))
  .links(links)
  .size([width, height])
  .linkDistance(50)
  .charge(-200)
  .on("tick", tick)
  .start();

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

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

var node = svg.selectAll(".node")
  .data(force.nodes())
  .enter().append("g")
  .attr("class", "node")
  .on("mouseover", mouseover)
  .on("mouseout", mouseout)
  .on("click", click)
  .on("dblclick", dblclick)
  .call(force.drag);

node.append("circle")
  .attr("r", radius)
  .style("fill", "#C71585");

node.append("text")
  .attr("x", 14)
  .attr("dy", ".35em")
  .style("fill", "#333")
  .text(function(d) {
    return d.name;
  });


function tick() {
  node.attr("transform", function(d) {
    var r = 16;
    d.x = Math.max(r, Math.min(width - r, d.x));
    d.y = Math.max(r, Math.min(height - r, d.y));
    return "translate(" + d.x + "," + d.y + ")"
  });

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

function mouseover() {
  d3.select(this).select("circle").transition()
    .duration(750)
    .attr("r", 16);
}

function mouseout() {
    d3.select(this).select("circle").transition()
      .duration(750)
      .attr("r", 12);
  }
  // action to take on mouse click

function click() {
  d3.select(this).select("text").transition()
    .duration(750)
    .attr("x", 22)
    .style("stroke-width", ".5px")
    .style("opacity", 1)
    .style("fill", "#E34A33")
    .style("font", "17.5px serif");
  d3.select(this).select("circle").transition()
    .duration(750)
    .style("fill", "#E34A33")
    .attr("r", 16)
}

// action to take on mouse double click
function dblclick() {
  d3.select(this).select("circle").transition()
    .duration(750)
    .attr("r", 12)
    .style("fill", "#E34A33");
  d3.select(this).select("text").transition()
    .duration(750)
    .attr("x", 14)
    .style("stroke", "none")
    .style("fill", "#E34A33")
    .style("stroke", "none")
    .style("opacity", 0.6)
    .style("font", "14px serif");
}
.link {
  stroke: #666;
  opacity: 0.6;
  stroke-width: 1.5px;
}
.node circle {
  stroke: #fff;
  opacity: 0.6;
  stroke-width: 1.5px;
}
text {
  font: 15px serif;
  opacity: 0.6;
  stroke: green;
  pointer-events: none;
}
svg{
  background-color: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>