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

D3.js带节点链接的聚类气泡分组失效问题求助

带节点链接的两层聚类气泡图分组失效问题

需求

我需要实现带节点链接的聚类气泡图,完成两层聚类:

  • 根据属性水平拆分,形成三个不同堆叠组;
  • 每个堆叠组内聚合同类型节点。

实现思路

我结合了两个D3相关的示例来实现该效果:

  1. 水平拆分节点为堆叠组的力导向图方案
  2. 堆叠组内聚合节点的聚类气泡图方案

我已完成初步实现,代码可在JSFiddle中查看。

代码实现

JavaScript代码

const m = 10 // number of groups;
const l = 3
const [width, height] = [1000, 600];
const color = d3.scaleOrdinal(d3.range(m), d3.schemeCategory10);

function renderChart(dataSource) {
  const svg = d3.select("body")
    .append("svg")
    .attr("width", width)
    .attr("height", height);

    const pack = () => d3.pack()
        .size([width, height])
        .padding(1)
        (d3.hierarchy(dataSource.nodes)
            .sum(d => d.value));

  const nodes = pack().leaves();

  const simulation = d3.forceSimulation(nodes)
    .force("x", d3.forceX(d => {
      if (d.data.level === 1) {
        return width / 3 - 100; // width/5 - 50 - 400;
      }
      return width / 3 * d.data.level; // width/5 * d.group - 400;
    }).strength(0.95))
    .force("y", d3.forceY(height / 2).strength(0.01))
    .force("center", d3.forceCenter(width / 2, height / 2))
    .force("cluster", forceCluster())
    .force("collide", forceCollide());


  const node = svg.append("g")
    .selectAll("circle")
    .data(nodes)
    .join("circle")
    .attr("cx", d => d.x)
    .attr("cy", d => d.y)
    .attr("fill", d => color(d.data.group));

  node.transition()
    .attrTween("r", d => {
      const i = d3.interpolate(0, d.r);
      return t => d.r = i(t);
    });

  // links
  const links = dataSource.links.map(d => Object.create(d));
  const link = svg.append("g")
    .selectAll("line")
    .data(links)
    .join("line")
    .classed('link', true)
    .style('stroke', '#999')
    .style("stroke-opacity", 0.75);

  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("cx", d => d.x)
      .attr("cy", d => d.y);
  }

  simulation
    .nodes(nodes)
    .on("tick", ticked);

    // 启用以下三行代码后分组功能失效
    simulation
        .force("link", d3.forceLink().id(function (d) { return d.data.id; }).strength(0.3))
        .force("link").links(links);
}

d3.json("https://gist.githubusercontent.com/ravengao/a548cc4a7dd38f1afe0fe4b31ba8901b/raw/fd86a4e94e31ca2497ee78e1006dbed798f62e67/sample_data_supp%253E0.4_new.json").then((d) => {
  const dataSource = {
    links: d.links,
    nodes: d.nodes.map(n => {
      n.group = Math.random() * m | 0;
      n.value = -Math.log(Math.random())
      return n;
    })
  };

  dataSource.nodes = ({
    children: Array.from(
      d3.group(
        dataSource.nodes,
        d => d.group
      ),
      ([, children]) => ({
        children
      })
    )
  });

  renderChart(dataSource);
});


function forceCluster() {
  const strength = 0.2;
  let nodes;

  function force(alpha) {
    const centroids = d3.rollup(nodes, centroid, d => d.data.group);
    const l = alpha * strength;
    for (const d of nodes) {
      const {
        x: cx,
        y: cy
      } = centroids.get(d.data.group);
      d.vx -= (d.x - cx) * l;
      d.vy -= (d.y - cy) * l;
    }
  }

  force.initialize = _ => nodes = _;

  return force;
}

function forceCollide() {
  const alpha = 0.4; // fixed for greater rigidity!
  const padding1 = 2; // separation between same-color nodes
  const padding2 = 6; // separation between different-color nodes
  let nodes;
  let maxRadius;

  function force() {
    const quadtree = d3.quadtree(nodes, d => d.x, d => d.y);
    for (const d of nodes) {
      const r = d.r + maxRadius;
      const nx1 = d.x - r,
        ny1 = d.y - r;
      const nx2 = d.x + r,
        ny2 = d.y + r;
      quadtree.visit((q, x1, y1, x2, y2) => {
        if (!q.length)
          do {
            if (q.data !== d) {
              const r = d.r + q.data.r + (d.data.group === q.data.data.group ? padding1 : padding2);
              let x = d.x - q.data.x,
                y = d.y - q.data.y,
                l = Math.hypot(x, y);
              if (l < r) {
                l = (l - r) / l * alpha;
                d.x -= x *= l, d.y -= y *= l;
                q.data.x += x, q.data.y += y;
              }
            }
          } while (q = q.next);
        return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
      });
    }
  }

  force.initialize = _ => maxRadius = d3.max(nodes = _, d => d.r) + Math.max(padding1, padding2);

  return force;
}

function centroid(nodes) {
  let x = 0;
  let y = 0;
  let z = 0;
  for (const d of nodes) {
    let k = d.r ** 2;
    x += d.x * k;
    y += d.y * k;
    z += k;
  }
  return {
    x: x / z,
    y: y / z
  };
}

function generateUUID() {
  var d = new Date().getTime();
  var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = (d + Math.random() * 16) % 16 | 0;
    d = Math.floor(d / 16);
    return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
  });
  return uuid;
};

HTML代码

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js"></script>

问题描述

未添加节点链接时,两层聚类功能均正常工作;但添加节点间链接后,分组功能失效。注释掉代码中启用forceLink的三行代码时分组正常,启用后分组丢失,恳请协助解决该问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 21:20:37