使用 d3JS 动态更新力导向图

问题描述 投票:0回答:1

我目前正在使用 d3JS 制作力导向图。 我对 d3 相当陌生,所以我的(有缺陷的)解决方案基于教程和文档。

简单地说,我有一个端点,它定期(每 5 秒)为我提供节点和链接列表的对象(在我的代码中是一个 prop),每当我尝试更新我的图表时,我最终都会在顶部重新绘制整个图表旧的。

这是我想要实现的目标:动态添加、删除和更新图表的图表,而无需:

  1. 重绘整个图表
  2. 在节点上施加一些疯狂的力,使它们四处移动,即使在模拟完成后也是如此。 (模拟应该只适用于更新的节点)

我使用 NuxtJS v3 作为我的主要框架,尽管我认为这不太相关。

这是我迄今为止的实现:

<template>
  <div id="graph">
    <svg id="graph-svg" width="960" height="600"></svg>
  </div>
</template>

我使用 svg 的 id 访问它并使用 d3 显示图形:

<script setup lang="ts">
import { ref,watch } from 'vue';
import * as d3 from 'd3';

const props = defineProps({
  data: {
    type: Object,
    required: true,
  },
});
const { data } = props;
const metaData = ref();
let nodes, edges, simulation, svg;
let link, node;

// Watch for changes in the data prop
watch(() => props.data, async (newVal) => {
  if (newVal) {
    // Update the metadata and nodes/edges arrays
    metaData.value = props.data.graphStatistics;
    nodes = Object.values(props.data.nodes);
    edges = Object.values(props.data.edges);

    // Initialize the graph
    await initGraph();
  }
}, { immediate: true });


// Initialize the graph
const initGraph = async () => {
  // Select the SVG element and set its dimensions
  svg = d3.select("#graph-svg")
      .attr("width", window.innerWidth)
      .attr("height", window.innerHeight);

  // Create the force simulation
  simulation = d3.forceSimulation(nodes)
      .force("link", d3.forceLink(edges).id((d: { id: any; }) => d.id).distance(300).strength(1))
      .force("charge", d3.forceManyBody().strength(-7000))
      .force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2))
      .force("x", d3.forceX().strength(0.5))
      .force("y", d3.forceY().strength(0.5))
      .force("collide", d3.forceCollide(10));

  // Append the links
  link = svg.append("g")
      .attr("stroke", "#999")
      .attr("stroke-opacity", 0.6)
      .selectAll("line")
      .data(edges, (d) => d.id)
      .join("line")
      .attr("stroke-width", 1);

  // Append the nodes
  node = svg.append("g")
      .attr("stroke", "#fff")
      .attr("stroke-width", 1.5)
      .selectAll("circle")
      .data(nodes, (d) => d.id)
      .join("circle")
      .attr("r", 10)
      .attr("fill", "#537B87");

  // Update the positions of the nodes and links on each tick of the simulation
  simulation.on("tick", () => {
    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("cx", d => d.x)
        .attr("cy", d => d.y);
  });
};

</script>

还应该注意的是,当重新绘制图表时,atm 会在左上角实例化,而不是在中心。

提前感谢,我已经完全放弃了,在这里询问是我最后的选择。我已经为此工作了大约一周,但仍然无法弄清楚。其他 SO 帖子没有给我带来任何帮助,因为它们没有实现平滑的更新图表。

再次感谢!!

typescript d3.js nuxtjs3
1个回答
0
投票

所以过了一段时间我找到了解决方案。我对结果非常满意,希望它可以帮助那些拼命寻找解决方案的其他人。

//initialize this at onMounted, no data is needed
initChart() {
     this.svg= d3.select(this.$refs.svg)
        .attr("viewBox", [-window.innerWidth / 2, -window.innerHeight / 2, window.innerWidth, window.innerHeight])//this will center the graph
        .attr("width", window.innerWidth)
        .attr("height", window.innerHeight);

     this.link = this.svg.append("g")
        .attr(...)//your styling
        .selectAll("line");
     this.node = this.chart.append("g")
        .attr(...)//your styling
        .selectAll("circle");
}

//create a method for the simulation ticks
ticked() {
     this.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);

      this.node
        .attr("cx", d => d.x)
        .attr("cy", d => d.y);
}

//here is the ACTUAL updating
updateChart({nodes, edges}) {
      // now this is probably the most important part, and comes directly from mike bostocks implementation
      // Make a shallow copy to protect against mutation, while
      // recycling old nodes to preserve position and velocity.
      const old = new Map(this.node.data().map(d => [d.id, d]));
      nodes = nodes.map(d => ({...old.get(d.id), ...d}));
      edges = edges.map(d => ({...d}));

      this.link = this.link
        .data(edges, d => [d.source, d.target])
        .join(enter => enter.insert("line", "circle")
          .attr(...))//your styling
     );

      this.node = this.node
        .data(nodes, d => d.id)
        .join(enter => enter.append("circle")
          .attr()//your styling
          .call(node => node.append("title")// this is a tooltip
            .text(d => d.name))
        );

     // this is also very important as it restarts the simulation so the graph actually looks like it is being updated in real time
      this.simulation.nodes(nodes);
      this.simulation.force("link").links(edges);
      this.simulation.alpha(1).restart().tick();
      this.ticked();
    },

我希望这有帮助!

© www.soinside.com 2019 - 2024. All rights reserved.