过滤和停用强制布局中的链接和文本

Filter and deactivate links and text in the force-layout

我在D3.js中使用力布局。 现在我可以通过使用过滤器从任何移动中停用一些节点。在我的例子中,我过滤掉的节点是循环 else 部分中的蓝色节点。 现在,当单击 "button" 时,所有红色节点都会增加 forceCollide 值的大小。

借助下面 if 部分中的循环,如果 firstTime 为 "true" ,则可以使所有节点正常移动。 现在,我的问题是,我还没有找到如何停用连接到已过滤蓝色节点的链接和文本?这意味着,我希望被过滤节点的文本也被过滤并被停用,链接也是如此。那么,我怎样才能从按属性过滤的节点中获取链接和文本的连接呢? 谢谢大家!

<!DOCTYPE html>
<meta charset="utf-8">
<style>
  .links line {
    stroke: #999;
    stroke-opacity: 0.6;
  }

  .nodes circle {
    stroke: #fff;
    stroke-width: 1.5px;
  }
</style>
<div class="centre jumbotron">

</div>
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<button>Click me</button>
<script>
  var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height");

  var g = svg.append("g");
  var firstTime = true;

 var colours = ["blue", "red", "green", "yellow"];

  var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().id(function(d) {
      return d.id;
    }))
    .force("charge", d3.forceManyBody())
    .force("center", d3.forceCenter(width / 2, height / 2))
    .force("collide", d3.forceCollide(function(d) {
        return d.r + 1;
    }));

  d3.json("https://jsonblob.com/api/6e520635-d35c-11e6-b16a-6b255c15b1a3", function(error, graph) {
    if (error) throw error;

    var link = g.append("g")
      .attr("class", "links")
      .selectAll("line")
      .data(graph.links)
      .enter().append("line")
      .attr("stroke-width", 1);

    var node = g.append("g")
      .attr("class", "nodes")
      .selectAll("circle")
      .data(graph.nodes)
      .enter().append("circle")
      .attr("r", 5)
      .attr("fill", (d, i) => colours[i % 2])
      .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

    var text = svg.append("g")
    .attr("class", "labels")
    .selectAll("text")
    .data(graph.nodes)
    .enter().append("text")
    .attr("dx", 12)
    .attr("dy", ".35em")
    .style("fill", (d, i) => colours[i % 2])
    .text(function(d) { return d.group });

    d3.select("button").on("click", function(d) {
    firstTime = false;
    node.filter(function() {
    return d3.select(this).attr("fill") === "red"
    }).each(d => d.r = 60);
    simulation.nodes(graph.nodes);
    simulation.alpha(0.8).restart();
    })


    simulation
      .nodes(graph.nodes)
      .on("tick", ticked);

    simulation.force("link")
      .links(graph.links);

    function ticked() {
      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;});


        if (firstTime) {
        node.attr("cx", d => d.x).attr("cy", d => d.y);
        } else { 

        node.filter(function(e) {
        return d3.select(this).attr("fill") != "blue"
        }).attr("cx", d => d.x).attr("cy", d => d.y);
        } 

      text
        .attr("x", function(d) { return d.x; }) 
        .attr("y", function(d) { return d.y; });
    }
  });

  function dragstarted(d) {
    if (!d3.event.active) simulation.alphaTarget(0.3).restart();
    d.fx = d.x;
    d.fy = d.y;
  }

  function dragged(d) {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }

  function dragended(d) {
    if (!d3.event.active) simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
  }
</script>

我在 tick 函数中为你提出的过滤器写了最后一个问题的答案,因为我不知道你也有链接和文本。现在你有一个不同的问题,你不能简单地将另一个问题的答案改编为当前的问题:它们是不同的问题。

对于这个,简单的解决方案是设置 fxfy 属性。根据 API:

To fix a node in a given position, you may specify two additional properties:

  • fx - the node’s fixed x-position
  • fy - the node’s fixed y-position

At the end of each tick, after the application of any forces, a node with a defined node.fx has node.x reset to this value and node.vx set to zero; likewise, a node with a defined node.fy has node.y reset to this value and node.vy set to zero. To unfix a node that was previously fixed, set node.fx and node.fy to null, or delete these properties.

因此,您可以在 "click" 函数中执行类似的操作:

d3.select("button").on("click", function(d) {

    //this will fix the blue nodes
    node.filter(function() {
        return d3.select(this).attr("fill") === "blue"
    }).each(function(d) {
        d.fx = d.x;
        d.fy = d.y;
    })

    node.filter(function() {
        return d3.select(this).attr("fill") === "red"
    }).each(function(d) {
        return d.r = 80
    });

    simulation.nodes(nodes);
    simulation.alpha(0.8).restart();
})

这是一个演示。如果点击按钮,红色圆圈会推开另一个nodes/texts,但蓝色圆圈留在原位。文本和链接保持连接。拖动其中任何一个都会释放它。

var width = 400;
var height = 300;

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

var nodes = [{
  name: "foo",
  color: "blue"
}, {
  name: "bar",
  color: "green"
}, {
  name: "baz",
  color: "red"
}, {
  name: "foofoo",
  color: "yellow"
}, {
  name: "foobar",
  color: "blue"
}, {
  name: "foobaz",
  color: "green"
}, {
  name: "barfoo",
  color: "red"
}, {
  name: "barbar",
  color: "yellow"
}, {
  name: "barbaz",
  color: "blue"
}];

var links = [{
  "source": 0,
  "target": 1
}, {
  "source": 0,
  "target": 2
}, {
  "source": 0,
  "target": 3
}, {
  "source": 1,
  "target": 3
}, {
  "source": 1,
  "target": 4
}, {
  "source": 2,
  "target": 5
}, {
  "source": 3,
  "target": 6
}, {
  "source": 1,
  "target": 7
}, {
  "source": 6,
  "target": 8
}, {
  "source": 0,
  "target": 7
}, {
  "source": 2,
  "target": 6
}, {
  "source": 3,
  "target": 8
}];

var simulation = d3.forceSimulation()
  .force("link", d3.forceLink())
  .force("charge", d3.forceManyBody().strength(-50))
  .force("center", d3.forceCenter(width / 2, height / 2))
  .force("collide", d3.forceCollide(function(d) {
    return d.r + 1;
  }));

var link = svg.selectAll(null)
  .data(links)
  .enter()
  .append("line")
  .style("stroke", "#ccc")
  .style("stroke-width", 1);

var node = svg.selectAll(null)
  .data(nodes)
  .enter()
  .append("circle")
  .attr("r", function(d) {
    return d.r = 10;
  })
  .attr("stroke", "gray")
  .attr("stroke-width", "2px")
  .attr("fill", function(d) {
    return d.color
  })
  .call(d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended));;

var text = svg.selectAll(null)
  .data(nodes)
  .enter()
  .append("text")
  .attr("pointer-events", "none")
  .style("fill", "black")
  .attr("dy", "-1em")
  .attr("dx", "-1em")
  .text(function(d) {
    return d.name;
  });

simulation.nodes(nodes);
simulation.force("link")
  .links(links);

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

  text.attr("x", function(d) {
    return d.x
  }).attr("y", function(d) {
    return d.y
  });

});

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function dragended(d) {
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}

d3.select("button").on("click", function(d) {
  node.filter(function() {
    return d3.select(this).attr("fill") === "blue"
  }).each(function(d) {
    d.fx = d.x;
    d.fy = d.y;
  })
  node.filter(function() {
    return d3.select(this).attr("fill") === "red"
  }).each(function(d) {
    return d.r = 80
  });
  simulation.nodes(nodes);
  simulation.alpha(0.8).restart();
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<button>Click me</button>
<br>