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

D3.js文本节点旋转后未定位至目标位置问题求助

问题:旋转文本节点后远离目标位置

尝试为节点添加旋转文本,预期文本位于节点侧边,但实际文本远离节点。

问题原因

当前代码中,文本先平移到节点坐标,再进行旋转,但未针对旋转后的坐标系调整文本的偏移位置。SVG的变换是按顺序执行的:先将文本移动到节点位置,旋转后文本的锚点位置没有向节点方向偏移,导致文本远离节点。

解决方案

在旋转后添加一个基于旋转坐标系的平移,让文本向节点方向偏移。同时可调整text-anchor和dy属性,优化文本对齐效果。

修改文本部分的核心代码:

var txt = g.selectAll('.txt')
    .data(nodes)
    .join('text')
    .text(d => d.name)
    .attr('class','txt')
    .attr('stroke','black')
    .attr('font-size',40)
    .attr('text-anchor','middle') // 改为居中锚点,更易调整位置
    .attr('dy', '.3em') // 统一垂直对齐基准
    .attr('transform', (d,i) => {
        let alpha = 30;
        if (d.y == 0) {
            alpha = -30;
        }
        // 先平移到节点位置→旋转→向旋转后的X轴正方向偏移(节点半径+间距)
        return `translate(${xScale(d.x)},${yScale(d.y)})rotate(${alpha})translate(${r + 10}, 0)`;
    })

如果希望保留原有的text-anchor: end设置,可改为向旋转后的X轴负方向偏移:

var txt = g.selectAll('.txt')
    .data(nodes)
    .join('text')
    .text(d => d.name)
    .attr('class','txt')
    .attr('stroke','black')
    .attr('font-size',40)
    .attr('text-anchor','end')
    .attr('dy', (d,i) => {
        if (d.y == 0) return '.3em'
        else return '-.3em'
    })
    .attr('transform', (d,i) => {
        let alpha = 30;
        if (d.y == 0) {
            alpha = -30;
        }
        return `translate(${xScale(d.x)},${yScale(d.y)})rotate(${alpha})translate(-${r + 10}, 0)`;
    })

完整代码

JavaScript代码

demo()

function demo() {
    var r = 5
    var color = 'steelblue'
    
    var width = 800
    var height = 600
    var margin = {top: 20, left: 50,right: 10, bottom: 50}
    var gwidth = width + margin.left + margin.right
    var gheight = height + margin.top + margin.bottom;

    const data = `
AAAA,BBB,CCC,DDDD,EEEEE,X,y,z,u,v,w
BBB,FFFF,GGGGG,HHHHH
DDDD,IIII,JJJJ,KKKK,LLL
`

    var nodes = []
    var links = []
    var dx = 20
    var dy = 20
    var x = 0
    var y = 0
    var alpha = Math.PI/180*70
    var parent = null
    data.trim().split("\n").forEach((line,i) => {
        line.split(",").forEach((elem,j)=> {
            if (elem == "") return
            var m = nodes.find(d => d.name == elem)
            if (m == undefined) {
                var node = {name:elem,x:x,y:y,parent:parent}
                nodes.push(node)
                parent = node
                x += dx
            }else {
                x = m.x + y*Math.cos(alpha)/Math.sin(alpha)                
                parent = m
            }
        })
        y += dy        
    })
    
    var xmin = d3.min(nodes,d => d.x)
    var xmax = d3.max(nodes,d => d.x)
    var ymin = d3.min(nodes,d => d.y)
    var ymax = d3.max(nodes,d => d.y)

    var xScale = d3.scaleLinear()
        .range([0,width])
        .domain([xmin-10,xmax+10])
    
    var yScale = d3.scaleLinear()
        .range([height,0])
        .domain([ymin-10,ymax+10])

    var svg = d3.select("body")
        .append("svg")
        .attr("width", gwidth)
        .attr("height", gheight)
        .style('border','1px solid red')
    
    var g = svg.append("g")
        .attr('transform',`translate(${margin.left},${margin.top})`);

    var xaxis = g.append("g")
        .attr("transform",`translate(0,${height})`)
        .call(d3.axisBottom(xScale)
              .tickFormat((d,i) => null)//i==0 ?  null:d)
              .tickSize(0)
             );
    
    var yaxis = g.append("g")
        .attr("transform", `translate(0,0)`)
        .call(d3.axisLeft(yScale)
              .tickFormat((d,i) => null)//i==0 ?  null:d)
              .tickSize(0)
             );

    [xaxis,yaxis].forEach((d) => d.call(g => g.select(".domain").remove()))
    
    
    var node = g.selectAll('.point')
        .data(nodes)
        .join('circle')
        .attr('class','point')
        .attr('cx',d => xScale(d.x))
        .attr('cy',d => {
            return yScale(d.y)
        })
        .attr('r',r)
        .attr('stroke',color)
        .attr('fill',color)

    var txt = g.selectAll('.txt')
        .data(nodes)
        .join('text')
        .text(d => d.name)
        .attr('class','txt')
        .attr('stroke','black')
        .attr('font-size',40)
        .attr('text-anchor','middle')
        .attr('dy', '.3em')
        .attr('transform', (d,i) => {
            let alpha = 30;
            if (d.y == 0) {
                alpha = -30;
            }
            return `translate(${xScale(d.x)},${yScale(d.y)})rotate(${alpha})translate(${r + 10}, 0)`;
        })
    
    function drawlink(d) {
        if (d.parent == null) return ""
        var ax = xScale(d.parent.x)
        var ay = yScale(d.parent.y)
        var bx = xScale(d.x)
        var by = yScale(d.y)
        var path = ['M',ax,ay,'L',bx,by]
        return path.join(' ')
    }

    var links = g.selectAll('.link')
        .data(nodes)
        .join('path')
        .attr('class','link')
        .attr('d',drawlink)
        .attr('stroke',color)
        .attr('stroke-width',r)
    
    return    
}

HTML代码

<script src="https://unpkg.com/d3@7.0.4/dist/d3.min.js"></script>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 19:45:19