You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

D3力导向图通过WebSocket实时更新报错:Uncaught TypeError: Cannot read properties of undefined (reading 'length')

解决D3力导向图WebSocket实时更新时的"Cannot read properties of undefined (reading 'length')"错误

首先,咱们先定位你遇到的核心问题:报错出现在simulation.nodes(graph.nodes)这一行,说明graph.nodes是undefined——这大概率是WebSocket数据解析的问题,其次你的代码没处理DOM元素的实时更新逻辑,即使数据解析对了,图表也不会正确刷新。

问题1:WebSocket数据解析错误

你在onmessage里写了:

graph = JSON.parse(e.data).message;

但你给出的WebSocket推送示例数据是完整的graph对象,没有外层的message字段!如果推送的原始数据就是{"nodes": [...], "links": [...]}这种结构,那JSON.parse(e.data).message就会返回undefined,导致graph变成undefined,自然graph.nodes也是undefined,调用simulation.nodes()时就会触发"读取undefined的length"错误——因为d3的力导向模拟需要数组,它会尝试读取数组的length属性,而undefined没有这个属性。

解决方法:
检查WebSocket推送的JSON结构,如果没有外层message字段,直接解析成graph对象:

comSocket.onmessage = function (e) {
  // 先解析数据,根据实际结构调整是否取message字段
  const receivedData = JSON.parse(e.data);
  // 如果推送数据外层有message就用它,否则直接用receivedData
  const newGraph = receivedData.message || receivedData;
  console.log(newGraph);
  // 后续调用更新逻辑
  updateGraph(newGraph);
};

问题2:未处理DOM元素的实时更新

你的初始代码只处理了第一次渲染的enter()阶段(创建初始节点和连线),但收到新数据时,没有处理新增元素、更新现有元素、删除多余元素这三个关键步骤,即使simulation更新了,DOM也不会同步变化,甚至可能引发其他错误。

解决方法:
把图表更新逻辑封装成一个函数,处理d3的enter/update/exit生命周期:

// 先初始化全局的link和node变量
let link;
let node;
const svg = d3.select("svg"),
      width = +svg.attr("width"),
      height = +svg.attr("height");
const color = d3.scaleOrdinal(d3.schemeCategory20);
const simulation = d3.forceSimulation()
  .force("link", d3.forceLink().id(function(d) { return d.id; }))
  .force("charge", d3.forceManyBody().strength(-2500))
  .force("center", d3.forceCenter(width / 2, height / 2));

// 定义更新图表的函数
function updateGraph(newGraph) {
  // 1. 更新连线
  link = svg.select(".links").selectAll("line").data(newGraph.links);
  // 删除多余的连线
  link.exit().remove();
  // 添加新的连线,并合并现有连线的属性
  link = link.enter().append("line")
    .attr("stroke-width", function(d) { return Math.sqrt(d.value); })
    .merge(link);

  // 2. 更新节点组(g元素,包含圆圈和文字)
  node = svg.select(".nodes").selectAll("g").data(newGraph.nodes);
  // 删除多余的节点组
  node.exit().remove();
  // 添加新的节点组,并合并现有节点组
  node = node.enter().append("g")
    .merge(node);

  // 3. 更新节点圆圈
  const circles = node.selectAll("circle").data(d => [d]); // 每个g对应一个circle数据
  circles.exit().remove();
  circles.enter().append("circle")
    .attr("r", 20)
    .merge(circles)
    .attr("fill", function(d) { return color(d.group); });

  // 4. 更新节点文字标签
  const labels = node.selectAll("text").data(d => [d]);
  labels.exit().remove();
  labels.enter().append("text")
    .attr('x', 16)
    .attr('y', 13)
    .merge(labels)
    .text(function(d) { return d.id; });

  // 5. 更新力导向模拟
  simulation.nodes(newGraph.nodes);
  simulation.force("link").links(newGraph.links);
  // 重启模拟,让图表重新布局
  simulation.alpha(1).restart();
}

// 初始化渲染初始图表
const initialGraph = { 
  "nodes": [ {"id": "Agent_1", "group": 1}, {"id": "Agent_2", "group": 2}, {"id": "Agent_3", "group": 1}, {"id": "Agent_4", "group": 3} ], 
  "links": [] 
};
// 先创建初始的links和nodes容器
svg.append("g").attr("class", "links");
svg.append("g").attr("class", "nodes");
updateGraph(initialGraph);

// WebSocket消息处理
const comSocket = new WebSocket( 'ws://' + window.location.host + '/ws/com/' );
comSocket.onmessage = function (e) {
  const receivedData = JSON.parse(e.data);
  const newGraph = receivedData.message || receivedData;
  console.log(newGraph);
  updateGraph(newGraph);
};

// 保持原来的ticked函数
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; });
  node
    .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
// 绑定ticked事件到模拟
simulation.on("tick", ticked);

关键说明

  1. 数据解析:一定要和WebSocket推送的结构匹配,如果你的Django后端推送的是包含message字段的JSON(比如{"message": {"nodes": [...], ...}}),就保留receivedData.message,否则直接用receivedData;
  2. DOM更新:通过data()重新绑定数据,exit().remove()删除不再需要的元素,enter()创建新元素,merge()合并enter和update阶段,确保新老元素都能正确更新;
  3. 模拟重启:调用simulation.alpha(1).restart()可以重置模拟的冷却系数,让图表重新进行力导向布局,适配新的节点和连线。

这样修改后,你的图表应该就能正常接收WebSocket推送的数据并实时更新了。

内容的提问来源于stack exchange,提问作者shined day

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 23:47:40