【问题标题】:How to update d3-force elements?如何更新 d3-force 元素?
【发布时间】:2023-03-13 22:01:01
【问题描述】:

我想在点击时更新d3-force 元素,但我发现有问题。 我想要的是:

  1. 先生成所有链接
  2. 然后生成所有节点,让节点可以覆盖链接

所以当点击元素应该是:

<svg>
  <line ...></line>
  <line ...></line>
  <line ...></line>
  <line ...></line>
  <g><circle ...></circle></g>
  <g><circle ...></circle></g>
  <g><circle ...></circle></g>
  <g><circle ...></circle></g>
  <g><circle ...></circle></g>
</svg>

但我明白了:

<svg>
  <g><circle ...></circle></g>
  <line ...></line>
  <line ...></line>
  <line ...></line>
  <line ...></line>
  <g><circle ...></circle></g>
  <g><circle ...></circle></g>
  <g><circle ...></circle></g>
  <g><circle ...></circle></g>
</svg>

const width = 800;
const height = 400;

const svg = d3.select("svg").attr("viewBox", [0, 0, width, height]);
let node = svg.selectAll(".node");
let link = svg.selectAll(".link");

const simulation = d3
  .forceSimulation()
  .force("center", d3.forceCenter(width / 2, height / 2))
  .on("tick", (d) => {
    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);

    node
      .attr("transform", d => `translate(${d.x}, ${d.y})`);
  });

const root = {
  x: width / 2,
  y: height / 2,
  level: 0,
  id: "1"
};

function genChildren(p) {
  const dist = 100 / (p.level + 1);
  const nodes = [{
      x: p.x - dist,
      y: p.y,
      level: p.level + 1,
      id: p.id + "1"
    },
    {
      x: p.x + dist,
      y: p.y,
      level: p.level + 1,
      id: p.id + "2"
    },
    {
      x: p.x,
      y: p.y - dist,
      level: p.level + 1,
      id: p.id + "3"
    },
    {
      x: p.x,
      y: p.y + dist,
      level: p.level + 1,
      id: p.id + "4"
    },
  ];
  const links = nodes.map(v => ({
    source: p.id,
    target: v.id
  }));
  return {
    nodes,
    links
  };
}


function update(nodes, links) {
  link = link.data(links)
    .join(enter => {
      return enter.append("line")
        .attr("stroke", "#000")
        .attr("stroke-width", "1.5px")
        .call(enter => enter.transition().attr("stroke-opacity", 1));
    }, update => update, exit => exit.remove());
  node = node.data(nodes, d => d.id)
    .join(
      enter => {
        const g = enter.append("g");
        g.append("circle")
          .attr("r", 12)
          .attr("cursor", "move")
          .attr("fill", "#ccc")
          .attr("stroke", "#000")
          .attr("stroke-width", "1.5px");
        return g;
      },
      update => update,
      exit => exit.remove(),
    )
    .attr("transform", function(d) {
      return "translate(" + d.x + ", " + d.y + ")";
    })
    .on("click", (ev, d) => {
      if (d.level !== 0) {
        return;
      }
      const childrenInfo = genChildren(root)
      const nodes = d.active ? [root] : [root].concat(childrenInfo.nodes);
      const links = d.active ? [] : childrenInfo.links;
      d.active = !d.active;
      update(nodes, links);
    });

  simulation.nodes(nodes).force("link", d3.forceLink(links).id(node => node.id).strength(-0.01));

  if (simulation.alpha() <= 1) {
    simulation.alpha(1);
    simulation.restart();
  }
}

update([root], [])
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg></svg>

【问题讨论】:

    标签: javascript html svg d3.js d3-force-directed


    【解决方案1】:

    您可以为节点和链接设置单独的g 元素,将链接添加到第一个,将节点添加到第二个。或者,您可以使用 node.raise() 将这些元素放在 DOM 中的所有链接之后:

    const width = 800;
    const height = 400;
    
    const svg = d3.select("svg").attr("viewBox", [0, 0, width, height]);
    let node = svg.selectAll(".node");
    let link = svg.selectAll(".link");
    
    const simulation = d3
      .forceSimulation()
      .force("center", d3.forceCenter(width / 2, height / 2))
      .on("tick", (d) => {
        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);
    
        node
          .attr("transform", d => `translate(${d.x}, ${d.y})`);
      });
    
    const root = {
      x: width / 2,
      y: height / 2,
      level: 0,
      id: "1"
    };
    
    function genChildren(p) {
      const dist = 100 / (p.level + 1);
      const nodes = [{
          x: p.x - dist,
          y: p.y,
          level: p.level + 1,
          id: p.id + "1"
        },
        {
          x: p.x + dist,
          y: p.y,
          level: p.level + 1,
          id: p.id + "2"
        },
        {
          x: p.x,
          y: p.y - dist,
          level: p.level + 1,
          id: p.id + "3"
        },
        {
          x: p.x,
          y: p.y + dist,
          level: p.level + 1,
          id: p.id + "4"
        },
      ];
      const links = nodes.map(v => ({
        source: p.id,
        target: v.id
      }));
      return {
        nodes,
        links
      };
    }
    
    
    function update(nodes, links) {
      link = link.data(links)
        .join(enter => {
          return enter.append("line")
            .attr("stroke", "#000")
            .attr("stroke-width", "1.5px")
            .call(enter => enter.transition().attr("stroke-opacity", 1));
        }, update => update, exit => exit.remove());
      node = node.data(nodes, d => d.id)
        .join(
          enter => {
            const g = enter.append("g");
            g.append("circle")
              .attr("r", 12)
              .attr("cursor", "move")
              .attr("fill", "#ccc")
              .attr("stroke", "#000")
              .attr("stroke-width", "1.5px");
            return g;
          },
          update => update,
          exit => exit.remove(),
        )
        .raise()
        .attr("transform", function(d) {
          return "translate(" + d.x + ", " + d.y + ")";
        })
        .on("click", (ev, d) => {
          if (d.level !== 0) {
            return;
          }
          const childrenInfo = genChildren(root)
          const nodes = d.active ? [root] : [root].concat(childrenInfo.nodes);
          const links = d.active ? [] : childrenInfo.links;
          d.active = !d.active;
          update(nodes, links);
        });
    
      simulation.nodes(nodes).force("link", d3.forceLink(links).id(node => node.id).strength(-0.01));
    
      if (simulation.alpha() <= 1) {
        simulation.alpha(1);
        simulation.restart();
      }
    }
    
    update([root], [])
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
    <svg></svg>

    【讨论】:

      猜你喜欢
      • 2014-07-14
      • 1970-01-01
      • 1970-01-01
      • 2019-06-04
      • 2019-05-11
      • 2012-11-15
      • 1970-01-01
      • 2011-11-28
      相关资源
      最近更新 更多