我目前正在使用 d3JS 制作力导向图。 我对 d3 相当陌生,所以我的(有缺陷的)解决方案基于教程和文档。
简单地说,我有一个端点,它定期(每 5 秒)为我提供节点和链接列表的对象(在我的代码中是一个 prop),每当我尝试更新我的图表时,我最终都会在顶部重新绘制整个图表旧的。
这是我想要实现的目标:动态添加、删除和更新图表的图表,而无需:
我使用 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 帖子没有给我带来任何帮助,因为它们没有实现平滑的更新图表。
再次感谢!!
所以过了一段时间我找到了解决方案。我对结果非常满意,希望它可以帮助那些拼命寻找解决方案的其他人。
//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();
},
我希望这有帮助!