移动 g 元素使它们在树形布局中居中

Move g element for centering them in a tree layout

我想移动 g 元素包含多个 tspan 文本以输入。

demo()

function demo() {
  //subtitle("iptable filter")
  // 2. 描画用のデータ準備
  var width = 800
  var height = 400
  var data = {name:"AAA\nBBB",children: [
    {name: "CCC\nDDD",children:[
      {name:"EEE\nFFF"}
    ]},
    {name: "GGG\nHHH",children:[
      {name:"III\nJJJ"}
    ]},
    {name: "KKK\nLLL",children: [
      {name: "MMM\nNNN"}
    ]},
    {name: "OOO\nPPP",children:[
      {name: "QQQ\nRRR"}
    ]}
  ]}
 
  var root = d3.hierarchy(data);
 
  var tree = d3.tree()
    .size([height, width])
 
  tree(root);
 
  var margin = {left:80,top:20,right:20,bottom:20}
  var svg = d3.select('body').append("svg")
  .attr('width',width + margin.left + margin.right)
  .attr('height',height + margin.top + margin.bottom)
  
  var g = svg.append("g")
    .attr("transform", `translate(${margin.left},${margin.top})`)
  
  var link = g.selectAll(".link")
    .data(root.descendants().slice(1))
    .enter()
    .append("path")
    .attr("class", "link")
    .attr("d", function(d) {
      return "M" + d.y + "," + d.x +
        "C" + (d.parent.y + 100) + "," + d.x +
        " " + (d.parent.y + 100) + "," + d.parent.x +
        " " + d.parent.y + "," + d.parent.x;
    });
 
  var node = g.selectAll(".node")
    .data(root.descendants())
    .enter()
    .append("g")
    .attr("class", "node")
    .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
    
  var txtnode = node.append("text")
    .attr("text-anchor", 'start')
    .attr("dominant-baseline","text-before-edge")//"middle"
    .attr("font-size", "200%")
  .selectAll('tspan')
  .data(d => d.data.name.split('\n'))
  .join('tspan')
  .attr('class','tspan')
  .attr('x',0)
  .attr('y',(d,i) => i*25)
  .text(d => d)
  
  node.each((d,i,n) =>{
    var bbox = d3.select(n[i]).node().getBBox()
    var margin = 4
    bbox.x -= margin
    bbox.y -= margin
    bbox.width += 2*margin
    bbox.height += 2*margin
    d.bbox = bbox
  })
   
  node.insert("rect",'text')
    .attr('fill','#FEFECE') 
    .attr('fill','none')
    .attr('rx',5.5)
    .attr('ry',5.5)
    .attr('stroke','#A80036')
    .attr('stroke-width',2)
    .attr('x',d => d.bbox.x)
    .attr('y',d => d.bbox.y)
    .attr('width',d => d.bbox.width)
    .attr('height',d => d.bbox.height)
  
    node.attr('dx',(d,i,n) => { 
    var x = d.bbox.width/2
    return -x
    })
    .attr('dy',(d,i,n) => {
        var x = d.bbox.width/2
    var y = d.bbox.height/2
    return -y
   })
     
  g.selectAll('.link')
  .attr('fill','none')
  .attr('stroke','#555')
  .attr('stroke-opacity',1)
  .attr('stroke-width',4)

}
<script src="https://d3js.org/d3.v6.min.js"></script>

属性 dx 和 dy 在此示例中不起作用。移动 g 元素以使其移动到中心的正确方法是什么?

为了动态地重新定位它们,一个简单的方法是获取元素的大小并将其 up/left 转换为 height/width 的一半:

node.each(function(d) {
    const thisSize = this.getBoundingClientRect();
    d3.select(this).attr("transform", `translate(${d.y - thisSize.width/2},${d.x - thisSize.height/2})`)
});

这里是你的代码有那个变化:

demo()

function demo() {
  //subtitle("iptable filter")
  // 2. 描画用のデータ準備
  var width = 800
  var height = 400
  var data = {
    name: "AAA\nBBB",
    children: [{
        name: "CCC\nDDD",
        children: [{
          name: "EEE\nFFF"
        }]
      },
      {
        name: "GGG\nHHH",
        children: [{
          name: "III\nJJJ"
        }]
      },
      {
        name: "KKK\nLLL",
        children: [{
          name: "MMM\nNNN"
        }]
      },
      {
        name: "OOO\nPPP",
        children: [{
          name: "QQQ\nRRR"
        }]
      }
    ]
  }

  var root = d3.hierarchy(data);

  var tree = d3.tree()
    .size([height, width])

  tree(root);

  var margin = {
    left: 80,
    top: 20,
    right: 20,
    bottom: 20
  }
  var svg = d3.select('body').append("svg")
    .attr('width', width + margin.left + margin.right)
    .attr('height', height + margin.top + margin.bottom)

  var g = svg.append("g")
    .attr("transform", `translate(${margin.left},${margin.top})`)

  var link = g.selectAll(".link")
    .data(root.descendants().slice(1))
    .enter()
    .append("path")
    .attr("class", "link")
    .attr("d", function(d) {
      return "M" + d.y + "," + d.x +
        "C" + (d.parent.y + 100) + "," + d.x +
        " " + (d.parent.y + 100) + "," + d.parent.x +
        " " + d.parent.y + "," + d.parent.x;
    });

  var node = g.selectAll(".node")
    .data(root.descendants())
    .enter()
    .append("g")
    .attr("class", "node");

  var txtnode = node.append("text")
    .attr("text-anchor", 'start')
    .attr("dominant-baseline", "text-before-edge") //"middle"
    .attr("font-size", "200%")
    .selectAll('tspan')
    .data(d => d.data.name.split('\n'))
    .join('tspan')
    .attr('class', 'tspan')
    .attr('x', 0)
    .attr('y', (d, i) => i * 25)
    .text(d => d)

  node.each((d, i, n) => {
    var bbox = d3.select(n[i]).node().getBBox()
    var margin = 4
    bbox.x -= margin
    bbox.y -= margin
    bbox.width += 2 * margin
    bbox.height += 2 * margin
    d.bbox = bbox
  })

  node.insert("rect", 'text')
    .attr('fill', '#FEFECE')
    .attr('fill', 'none')
    .attr('rx', 5.5)
    .attr('ry', 5.5)
    .attr('stroke', '#A80036')
    .attr('stroke-width', 2)
    .attr('x', d => d.bbox.x)
    .attr('y', d => d.bbox.y)
    .attr('width', d => d.bbox.width)
    .attr('height', d => d.bbox.height)

  node.attr('dx', (d, i, n) => {
      var x = d.bbox.width / 2
      return -x
    })
    .attr('dy', (d, i, n) => {
      var x = d.bbox.width / 2
      var y = d.bbox.height / 2
      return -y
    })

  g.selectAll('.link')
    .attr('fill', 'none')
    .attr('stroke', '#555')
    .attr('stroke-opacity', 1)
    .attr('stroke-width', 4)

  node.each(function(d) {
    const thisSize = this.getBoundingClientRect();
    d3.select(this).attr("transform", `translate(${d.y - thisSize.width/2},${d.x - thisSize.height/2})`)
  });

}
<script src="https://d3js.org/d3.v6.min.js"></script>