簡體   English   中英

d3.js中如何自動同時移動節點和鏈接

[英]How to automatically move nodes and links at the same time in d3.js

我正在嘗試 d3.js(v7) 中的強制示例 在這個示例中,當我拖動一個節點時,鏈接和其他節點會同步移動。 我希望所有節點始終隨機移動,並且我希望其他鏈接和節點與它們同步移動,就像我在拖動它們一樣。 代碼如下。 json 文件與示例相同。 當我運行此代碼時,節點會移動,但鏈接不會跟隨移動並保持靜止。

function ForceGraph({
    nodes, // an iterable of node objects (typically [{id}, …])
    links, // an iterable of link objects (typically [{source, target}, …])
  }, {
    nodeId = d => d.id, // given d in nodes, returns a unique identifier (string)
    nodeGroup, // given d in nodes, returns an (ordinal) value for color
    nodeGroups, // an array of ordinal values representing the node groups
    nodeStrength,
    linkSource = ({source}) => source, // given d in links, returns a node identifier string
    linkTarget = ({target}) => target, // given d in links, returns a node identifier string
    linkStrokeWidth = 10, // given d in links, returns a stroke width in pixels
    linkStrength = 0.55,
    colors = d3.schemeTableau10, // an array of color strings, for the node groups
    width = 640, // outer width, in pixels
    height = 400, // outer height, in pixels
    invalidation // when this promise resolves, stop the simulation
  } = {}) {
    // Compute values.
    const N = d3.map(nodes, nodeId).map(intern);
    const LS = d3.map(links, linkSource).map(intern);
    const LT = d3.map(links, linkTarget).map(intern);
  
    if (nodeTitle === undefined) nodeTitle = (_, i) => N[i];
    const T = nodeTitle == null ? null : d3.map(nodes, nodeTitle);
    const G = nodeGroup == null ? null : d3.map(nodes, nodeGroup).map(intern);
    const W = typeof linkStrokeWidth !== "function" ? null : d3.map(links, linkStrokeWidth);
  
    // Replace the input nodes and links with mutable objects for the simulation.
    nodes = d3.map(nodes, (_, i) => ({id: N[i], type: NODETYPES[i], tag: parsed_NODETAGS[i], texts: X[i]}));
    links = d3.map(links, (_, i) => ({source: LS[i], target: LT[i]}));
  
    // Compute default domains.
    if (G && nodeGroups === undefined) nodeGroups = d3.sort(G);
  
    // Construct the scales.
    const color = nodeGroup == null ? null : d3.scaleOrdinal(nodeGroups, colors);
  
    // Construct the forces.
    const forceLink = d3.forceLink(links).id(({index: i}) => N[i]);
    if (nodeStrength !== undefined) forceNode.strength(nodeStrength);
    if (linkStrength !== undefined) forceLink.strength(linkStrength);
  
    const zoom = d3.zoom()
        .scaleExtent([1, 40])
        .on("zoom", zoomed);
  
    const svg = d3.create("svg")
        .attr("viewBox", [-width / 2, -height / 2.5, width, height])
        .on("click", reset)
        .attr("style", "max-width: 100%; height: auto; height: intrinsic;");
    
    svg.call(zoom);
    const g = svg.append("g");
  
    const link = g.append("g")
      .selectAll("line")
      .data(links)
      .join("line");
  
    const simulation = d3.forceSimulation(nodes)
      .force("link", forceLink)
      .force("charge", d3.forceManyBody())
      .force("center",  d3.forceCenter())
      .on("tick", ticked);
    
    const node = g.append("g")
        .attr("class", "nodes")
        .style("opacity", 1.0)
      .selectAll("circle")
      .data(nodes)
      .join("circle")
        .attr("r", 5)
        .call(drag(simulation));
  
    if (W) link.attr("stroke-width", ({index: i}) => W[i]);
    if (G) node.attr("fill", ({index: i}) => color(G[i]));

    function random(){
        node
          .transition()
          .duration(2000)
          .attr("cx", function(d){
            return d.x + Math.random()*80 - 40;
          })
          .attr("cy", function(d){
            return d.y + Math.random()*80 - 40;
          });
      }
    setInterval(random, 800);
    if (invalidation != null) invalidation.then(() => simulation.stop());
  
    function intern(value) {
      return value !== null && typeof value === "object" ? value.valueOf() : value;
    }
  
    function ticked() {
      node
        .attr("cx", d => d.x)
        .attr("cy", d => d.y);
  
      link
        .attr("x1", d => d.source.x)
        .attr("y1", d => d.source.y)
        .attr("x2", d => d.target.x)
        .attr("y2", d => d.target.y);
    }

    function drag(simulation) {    
      function dragstarted(event) {
        if (!event.active) simulation.alphaTarget(0.3).restart();
        event.subject.fx = event.subject.x;
        event.subject.fy = event.subject.y;
      }
      function dragged(event) {
        event.subject.fx = event.x;
        event.subject.fy = event.y;
      }
      function dragended(event) {
        if (!event.active) simulation.alphaTarget(0);
        event.subject.fx = null;
        event.subject.fy = null;
      }
      return d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended);
    }

    function zoomed({transform}) {
      g.attr("transform", transform);
    }
  
    return Object.assign(svg.node(), {});
}

在您的 function random()中,您不會更改基礎數據,而只會更改其表示方式。 每個circle都包含對nodes數組中一個元素的引用,但是您在random()中設置cxcy ,您不會更新基礎數據dxdy 即便如此,圓對cxcy的值也不是反應性的。 也就是說,當dxdy發生變化時,它們不會被重新評估。

所以我會拆分你的代碼。 有一個 function random()每 800 毫秒調用一次,並通過更改dxdy來稍微調整節點。 然后simulation負責實際繪制圓圈和鏈接 - 它似乎已經這樣做了。

 const size = 500; const nodes = [{ id: 'A', x: 150, y: 150 }, { id: 'B', x: 250, y: 250 }, { id: 'C', x: 350, y: 350 } ]; const links = [{ source: nodes[0], target: nodes[1] }, { source: nodes[0], target: nodes[2] } ]; const svg = d3.select('body').append('svg').attr('width', size).attr('height', size).attr('border', 'solid 1px red') const g = svg.append('g'); const node = g.append("g").attr("class", "nodes").selectAll("circle").data(nodes).join("circle").attr("r", 5); const link = g.append("g").attr("class", "links").selectAll("line").data(links).join("line"); const simulation = d3.forceSimulation(nodes).force("link", d3.forceLink(links).strength(2)).force("charge", d3.forceManyBody().strength(2)).force("center", d3.forceCenter(size / 2, size / 2).strength(0.05)).on("tick", ticked); function ticked() { node.attr("cx", d => dx).attr("cy", d => dy); link.attr("x1", d => d.source.x).attr("y1", d => d.source.y).attr("x2", d => d.target.x).attr("y2", d => d.target.y); } function random() { simulation.stop(); nodes.forEach(d => { dx += Math.random() * 80 - 40; dy += Math.random() * 80 - 40; }); node.transition(1000).attr("cx", d => dx).attr("cy", d => dy); link.transition(1000).attr("x1", d => d.source.x).attr("y1", d => d.source.y).attr("x2", d => d.target.x).attr("y2", d => d.target.y).on('end', () => { simulation.restart(); }); } setInterval(random, 2000);
 .links>line { stroke: black; stroke-width: 2px; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.1.1/d3.min.js"></script>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM