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

如何在D3力导向图中添加复合节点并参与布局与连边

D3力导向图添加复合矩形节点并参与布局的实现方案

已创建D3力导向布局图,同时制作了一款矩形带文字的复合节点。希望实现该复合节点与现有圆形节点建立边连接,且能参与力导向布局。推测需要将nodes与labelNodes合并后传入.forceSimulation(nodes),但不清楚具体操作方法。

核心实现步骤

  • 合并节点数据:将复合节点数据合并到主节点数组,确保每个节点id唯一无冲突
  • 更新力模拟:把合并后的节点数组传入力模拟并重启,让新节点参与布局计算
  • 同步位置更新:在ticked函数中通过transform调整复合节点的位置,保证中心对齐布局坐标
  • 绑定拖拽交互:给复合节点添加拖拽事件,使其和圆形节点具备相同交互能力
  • 添加连接边:在边数据中加入指向复合节点的连接,让力布局计算节点间的关联关系

修改后的完整代码

<script src="https://d3js.org/d3.v7.min.js" charset="utf-8"></script>
<svg ref="chart" id="chart" class="graph"></svg>
.graph {
  width: 1000px;
  height: 400px;      
}
//
// 创建力导向图基础结构
//

const width = 1000
const height = 400

const node_data = Array.from({ length: 5 }, () => ({
  group: Math.floor(Math.random() * 3),
}))

// 扩展边数据,加入指向复合节点的连接
const edge_data = [
  ...Array.from({ length: 10 }, () => ({
    source: Math.floor(Math.random() * 5),
    target: Math.floor(Math.random() * 5),
    value: Math.floor(Math.random() * 10) + 1,
  })),
  {source: 0, target: 5, value: 5},
  {source: 1, target: 5, value: 5}
]

const links = edge_data.map((d) => ({ ...d }))
const nodes = node_data.map((d, index) => ({ id: index, ...d }))
const color = d3.scaleOrdinal(d3.schemeCategory10)

//
// 合并复合节点数据到主节点数组
//
const labelNodes = [
  {
    id: 5, // 使用不冲突的ID
    name: 'A title',
    type: 'composite' // 标记节点类型,用于区分渲染
  },
]
nodes.push(...labelNodes)

const svg = d3.select('#chart')

const simulation = d3
  .forceSimulation(nodes)
  .force(
    'link',
    d3
      .forceLink(links)
      .id((d) => d.id)
      .distance((d) => 100)
  )
  .force('charge', d3.forceManyBody())
  .force('center', d3.forceCenter(width / 2, height / 2))
  .on('tick', ticked)

const link = svg
  .append('g')
  .attr('stroke', '#999')
  .attr('stroke-opacity', 0.6)
  .selectAll()
  .data(links)
  .join('line')
  .attr('stroke-width', (d) => Math.sqrt(d.value))

// 渲染圆形节点(过滤非复合节点)
const circleNode = svg
  .append('g')
  .attr('stroke', '#fff')
  .attr('stroke-width', 1.5)
  .selectAll()
  .data(nodes.filter(d => d.type !== 'composite'))
  .join('circle')
  .attr('r', 16)
  .attr('fill', (d) => color(d.group))

circleNode.append('title').text((d) => `hello ${d.id}`)

//
// 渲染复合节点(过滤复合节点)
//
const compositeGroup = svg
  .append('g')
  .attr('id', 'composite')
  .selectAll('g')
  .data(nodes.filter(d => d.type === 'composite'), (d) => d.id)

const g = compositeGroup.enter().append('g')

g.append('rect')
  .attr('class', 'node')
  .attr('rx', '15')
  .attr('width', 200)
  .attr('height', 25)
  .attr('fill', '#dceed3')

g.append('text')
  .attr('class', 'name')
  .attr('dominant-baseline', 'middle')
  .attr('font-size', '10px')
  .attr('x', 13)
  .attr('y', 13)
  .text((d) => d.name)

const compositeNode = g.merge(compositeGroup)

// 给复合节点绑定拖拽事件
compositeNode.call(d3.drag().on('start', dragstarted).on('drag', dragged).on('end', dragended))

function ticked() {
  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)

  // 更新圆形节点位置
  circleNode.attr('cx', (d) => d.x).attr('cy', (d) => d.y)

  // 更新复合节点位置:将布局中心坐标转换为g元素的transform,偏移矩形左上角实现中心对齐
  compositeNode.attr('transform', d => {
    return `translate(${d.x - 100}, ${d.y - 12.5})`
  })
}

function dragstarted(event) {
  if (!event.active) simulation.alphaTarget(0.3).restart()
  event.subject.fx = event.subject.x
  event.subject.fy = event.subject.y
}

function dragged(event) {
  event.subject.fx = event.x
  event.subject.fy = event.y
}

function dragended(event) {
  if (!event.active) simulation.alphaTarget(0)
  event.subject.fx = null
  event.subject.fy = null
}

关键改动说明

  1. 节点数据合并:给复合节点设置唯一ID(5)并标记类型,避免和原有节点冲突,直接合并到主节点数组
  2. 边数据扩展:新增连接到复合节点的边,让力布局能计算节点间的引力关系
  3. 分类型渲染:通过filter分别处理圆形节点和复合节点的渲染逻辑,避免渲染混淆
  4. 位置对齐调整:在ticked函数中通过transform偏移复合节点的g元素,保证节点中心与布局坐标对齐
  5. 交互同步:给复合节点绑定和圆形节点一致的拖拽事件,保持交互体验统一

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 00:04:57