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

React中使用D3构建径向树时数据层级配置异常问题

解决D3径向树无法识别扁平层级数据及节点不显示问题

核心问题分析

  • 数据结构不匹配:d3.hierarchy() 接收的是嵌套结构数据(每个节点包含children数组),而你传入的是扁平的id/parentId数组,无法生成正确层级;d3.stratify()报错是因为未识别到根节点(根节点的parentId需为null/undefined或不存在)。
  • 节点未定位:你注释掉了circle的transform属性,导致节点没有被放置到正确位置,页面无内容。
  • 未清除旧元素:每次data更新时,SVG会重复添加元素,造成冗余。

解决方案步骤

1. 用d3.stratify()转换扁平数据为层级结构

确保数据中根节点的parentId为null或未定义,通过stratify指定id和父id的访问器:

const stratify = d3.stratify()
  .id(d => d.id) // 指定节点id字段
  .parentId(d => d.parentId); // 指定父节点id字段

const root = stratify(data); // 转换为层级结构

2. 修正径向树节点的定位

取消注释circle的transform属性,同时给整个树的容器添加居中偏移(径向树默认以SVG原点为中心):

const svg = d3.select(svgRef.current);
// 先清除旧内容,避免重复渲染
svg.selectAll("*").remove();

// 创建主容器,居中到SVG中心
const g = svg.append("g")
  .attr("transform", `translate(${svg.attr("width")/2}, ${svg.attr("height")/2})`);

// 绘制连线
g.append("g")
  .attr("fill", "none")
  .attr("stroke", "#555")
  .attr("stroke-opacity", 0.4)
  .attr("stroke-width", 1.5)
  .selectAll("path")
  .data(root.links())
  .join("path")
  .attr("d", d3.linkRadial()
    .angle(d => d.x)
    .radius(d => d.y));

// 绘制节点,恢复transform属性
g.append("g")
  .selectAll("circle")
  .data(root.descendants())
  .join("circle")
  .attr("transform", d => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`)
  .attr("fill", d => d.children ? "#555" : "#999")
  .attr("r", 2.5);

3. 完整修改后的组件代码

import { useState, useEffect, useRef } from "react";
import * as d3 from "d3";

export default function Tree({ data }) {
  const svgRef = useRef();

  useEffect(() => {
    if (!data || data.length === 0) return;

    // 配置径向树布局
    const tree = d3.cluster()
      .size([2 * Math.PI, Math.min(928, 928) / 2 - 30])
      .separation((a, b) => (a.parent === b.parent ? 1 : 2) / a.depth);

    // 转换扁平数据为层级结构
    const stratify = d3.stratify()
      .id(d => d.id)
      .parentId(d => d.parentId);

    const root = stratify(data);
    // 计算树布局
    const treeRoot = tree(root);

    draw(treeRoot);
  }, [data]);

  function draw(root) {
    const svg = d3.select(svgRef.current);
    // 清除旧内容,避免重复渲染
    svg.selectAll("*").remove();

    // 居中容器
    const g = svg.append("g")
      .attr("transform", `translate(${svg.attr("width")/2}, ${svg.attr("height")/2})`);

    // 绘制连线
    g.append("g")
      .attr("fill", "none")
      .attr("stroke", "#555")
      .attr("stroke-opacity", 0.4)
      .attr("stroke-width", 1.5)
      .selectAll("path")
      .data(root.links())
      .join("path")
      .attr("d", d3.linkRadial()
        .angle(d => d.x)
        .radius(d => d.y));

    // 绘制节点
    g.append("g")
      .selectAll("circle")
      .data(root.descendants())
      .join("circle")
      .attr("transform", d => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`)
      .attr("fill", d => d.children ? "#555" : "#999")
      .attr("r", 2.5);
  }

  return (
    <div>
      <svg ref={svgRef} id="myTree" width="1000" height="1000"></svg>
    </div>
  );
}

额外注意事项

  • 确保数据中存在且仅存在一个根节点(即parentId为null/undefined的节点),否则d3.stratify()会抛出"no root node"错误。
  • 如果你的parentId字段是空字符串等特殊值,需要在访问器中处理:.parentId(d => d.parentId || null)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 17:06:08