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

如何在D3 v7中结合d3.pack创建螺旋布局气泡图?

问题描述

我已经用d3.pack实现了气泡图,但希望将气泡以螺旋布局排列。以下是我的实现代码及当前效果,请教如何调整气泡位置,使其呈现螺旋样式。

当前代码

const sortBubble = () => {
        setShowBackButton(true)

        const svg = d3.select(svgReference.current)
        svg.selectAll('*').remove()

        const colorScale = createColorScale()
        const valueScale = createValueScale(sortChartData)

        svg.append('g').attr('class', 'circle-group')

        const root = d3
            .hierarchy({
                children: sortChartData
            })
            .sum(d => d.value)
            .sort((a, b) => a.value - b.value)
        const nodes = root.descendants().filter(d => !d.children)

        d3
            .pack()
            .size([width / 2, height / 2])
            .padding(15)(root)

        svg
            .selectAll('.bubbleNode')
            .data(nodes)
            .enter()
            .append('circle')
            .attr('class', 'bubbleNode')
            .attr('cx', d => d.x ?? 0)
            .attr('cy', d => d.y ?? 0)
            .attr(
                'r',
                d =>
                    calculateRadius(d, bubbleData, valueScale) + COLLISION_RADIUS_FACTOR
            )
            .attr('fill', d => colorScale(d.value))
            .attr('transform', d => `translate(${d.x},${d.y})`)
    }

效果说明

  • 当前效果:气泡以紧凑的矩形区域排列
  • 目标样式:气泡按螺旋轨迹从中心向外排列,大的气泡在内层,小的在外层

解决方案

要实现螺旋布局,你需要替换d3.pack的布局计算逻辑,手动根据螺旋公式计算每个气泡的位置。核心思路是按气泡大小排序(大的在前),依次计算每个气泡在螺旋上的坐标,确保气泡之间不重叠。

修改后的代码示例

const sortBubble = () => {
    setShowBackButton(true)

    const svg = d3.select(svgReference.current)
    svg.selectAll('*').remove()

    const colorScale = createColorScale()
    const valueScale = createValueScale(sortChartData)
    const centerX = width / 2; // 螺旋中心X坐标
    const centerY = height / 2; // 螺旋中心Y坐标
    const spiralGap = 30; // 螺旋每圈的间距
    let currentAngle = 0;
    let currentRadius = 0;
    let lastRadius = 0;

    svg.append('g').attr('class', 'circle-group')

    // 按气泡大小降序排列,让大气泡处于螺旋内层
    const sortedNodes = [...sortChartData].sort((a, b) => b.value - a.value)
        .map(d => ({
            ...d,
            r: calculateRadius(d, bubbleData, valueScale) + COLLISION_RADIUS_FACTOR
        }));

    svg
        .selectAll('.bubbleNode')
        .data(sortedNodes)
        .enter()
        .append('circle')
        .attr('class', 'bubbleNode')
        .attr('r', d => d.r)
        .attr('fill', d => colorScale(d.value))
        .attr('cx', (d) => {
            // 计算当前气泡的螺旋半径与角度
            currentRadius = lastRadius + d.r + spiralGap;
            currentAngle += (d.r + lastRadius) / currentRadius;
            const x = centerX + currentRadius * Math.cos(currentAngle);
            lastRadius = d.r;
            return x;
        })
        .attr('cy', () => {
            const y = centerY + currentRadius * Math.sin(currentAngle);
            return y;
        });
}

关键修改点

  • 移除d3.pack()的布局计算,不再依赖它生成的x/y坐标
  • 将数据按气泡大小降序排序,确保大气泡在螺旋内层
  • 用极坐标转直角坐标的公式计算位置:x = 中心X + 半径 * cos(角度),y = 中心Y + 半径 * sin(角度)
  • 通过spiralGap参数调整螺旋圈间距,可根据实际效果微调

优化建议

如果需要更精确的碰撞避免(防止气泡重叠),可以结合d3-force的碰撞检测:

// 在生成气泡后添加力导向碰撞检测
const simulation = d3.forceSimulation(sortedNodes)
    .force('collision', d3.forceCollide().radius(d => d.r + 5))
    .on('tick', () => {
        svg.selectAll('.bubbleNode')
            .attr('cx', d => d.x)
            .attr('cy', d => d.y);
    });

这样既保留螺旋的整体轨迹,又能确保气泡之间不会重叠。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 08:05:34