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

如何在D3.js树形图中实现根节点从右向左布局并将连接线移至右侧

Got it, let's fix your D3.js tree to meet both requirements—right-to-left layout starting from the root, and links attached to the right side of nodes. Here's the breakdown of changes and the updated code:

Key Modifications Explained

1. Correct Right-to-Left Layout Configuration

  • Fixed orientation mapping: Updated the coordinate logic for right-to-left flow, so the root sits at the rightmost edge and children extend leftward.
  • Layout size alignment: Tied the tree layout's size to the orientation's defined dimensions instead of hardcoding values.
  • Depth-based positioning: Adjusted node y values so deeper nodes move left (instead of right) relative to the root.
  • Node rectangle offset: Shifted the node's x-position left by its full width, so its right edge aligns with the layout's coordinate origin—this ensures links start directly from the node's right edge.
  • Revised diagonal path: Rewrote the curve function to connect the right edge of parent nodes to the right edge of child nodes.
  • Arrow marker fix: Switched to marker-end and set auto-orientation so arrows point correctly toward child nodes in the right-to-left flow.

Updated Full Code

var treeData = { 
  "name": "NODE NAME 1", 
  "subname": "CODE N1", 
  "fill":"orange", 
  "children": [ 
    { "name": "NODE NAME 2.1", "subname": "CODE N1", "fill":"blue" }, 
    { "name": "NODE NAME 2.2","subname": "CODE N1" ,"fill":"blue" }, 
    { "name": "NODE NAME 2.3","subname": "CODE N1","fill":"blue", 
      "children": [ 
        { "name": "NODE NAME 3.3","fill":"blue","subname": "CODE N1", 
          "children": [{ "name":"NODE NAME 4.1","subname": "CODE N1", "fill":"#d281d2" } ] }, 
        { "name": "NODE NAME 3.4","fill":"blue", "subname": "CODE N1" , 
          "children": [{ "name":"NODE NAME 4.2", "subname": "CODE N1" , "fill":"#d281d2" } ] } 
      ] 
    } 
  ] 
}; 

// Set the dimensions and margins of the diagram 
var margin = {top: 100, right: 30, bottom: 200, left: 30},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom; 

// Fixed orientation configuration for right-to-left flow
var orientations = { 
  "right_to_left": { 
    size: [height, width], 
    x: function(d) { return width - d.y; }, 
    y: function(d) { return d.x; } 
  } 
}; 

// Clear existing SVG elements for redraw
d3.select("#tree-container").selectAll("*").remove(); 

// Append SVG and main group element
var svg = d3.select("#tree-container").append("svg")
  .attr("width", width + margin.right + margin.left)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); 

var i = 0, duration = 750, root; 
var o = orientations.right_to_left; 

// Declare tree layout with orientation-specific size
var treemap = d3.tree().size(o.size); 

// Create hierarchy from data
root = d3.hierarchy(treeData, function(d) { return d.children; }); 
root.x0 = height / 2; 
root.y0 = 0; 

// Collapse nodes beyond the second level initially
root.children.forEach(collapse); 
update(root); 

// Collapse a node and all its children
function collapse(d) { 
  if(d.children) { 
    d._children = d.children; 
    d._children.forEach(collapse); 
    d.children = null; 
  } 
} 

function update(source) { 
  // Compute new tree layout
  var treeData = treemap(root); 
  var nodes = treeData.descendants(),
      links = treeData.descendants().slice(1); 

  // Normalize depth: root at rightmost (y=0), children move left 180px per depth level
  nodes.forEach(function(d){ 
    d.y = d.depth * 180; 
  }); 

  // ****************** Nodes section *************************** 
  var node = svg.selectAll('g.node')
    .data(nodes, function(d) {return d.id || (d.id = ++i); }); 

  // Enter new nodes at the parent's previous position
  var nodeEnter = node.enter().append('g')
    .attr('class', 'node')
    .attr("transform", function(d) { 
      return "translate(" + o.x(source) + "," + o.y(source) + ")"; 
    })
    .on('click', click); 

  var rectHeight = 60, rectWidth = 120; 

  // Node rectangle: shift left so right edge aligns with layout x-coordinate
  nodeEnter.append('rect')
    .attr('class', 'node')
    .attr("width", rectWidth)
    .attr("height", rectHeight)
    .attr("x", -rectWidth) 
    .attr("y", -rectHeight/2) 
    .attr("rx","5")
    .style("fill", function(d) { return d.data.fill || "#fff"; }); 

  // Node labels: position inside the shifted rectangle
  nodeEnter.append('text')
    .attr("dy", "-0.5em")
    .attr("x", -rectWidth + 10) 
    .attr("text-anchor", "start")
    .text(function(d) { return d.data.name; })
    .append("tspan")
    .attr("dy", "1.2em")
    .attr("x", -rectWidth + 10)
    .text(function(d) { return d.data.subname; }); 

  // Update existing nodes
  var nodeUpdate = nodeEnter.merge(node); 

  // Transition to correct position using orientation coordinates
  nodeUpdate.transition()
    .duration(duration)
    .attr("transform", function(d) { 
      return "translate(" + o.x(d) + "," + o.y(d) + ")"; 
    }); 

  // Exit nodes with transition
  var nodeExit = node.exit().transition()
    .duration(duration)
    .attr("transform", function(d) { 
      return "translate(" + o.x(source) + "," + o.y(source) + ")"; 
    })
    .remove(); 

  nodeExit.select('rect')
    .attr('width', 1e-6)
    .attr('height', 1e-6); 

  nodeExit.select('text')
    .style('fill-opacity', 1e-6); 

  // ****************** Links section *************************** 
  var link = svg.selectAll('path.link')
    .data(links, function(d) { return d.id; }); 

  // Enter new links at parent's old position
  var linkEnter = link.enter().insert('path', "g")
    .attr("class", "link")
    .attr('d', function(d){ 
      var o2 = {x: source.x0, y: source.y0}; 
      return diagonal(o2, o2); 
    })
    .attr("marker-end", "url(#triangle)"); 

  // Update existing links
  var linkUpdate = linkEnter.merge(link); 

  linkUpdate.transition()
    .duration(duration)
    .attr('d', function(d){ return diagonal(d, d.parent) }); 

  // Exit links with transition
  var linkExit = link.exit().transition()
    .duration(duration)
    .attr('d', function(d) { 
      var o2 = {x: source.x, y: source.y}; 
      return diagonal(o2, o2); 
    })
    .remove(); 

  // Arrow marker: auto-orient to match path direction
  svg.append("svg:defs").append("svg:marker")
    .attr("id", "triangle")
    .attr("refX", 12) 
    .attr("refY", 6)
    .attr("markerWidth", 24)
    .attr("markerHeight", 24)
    .attr("orient", "auto")
    .append("path")
    .attr("d", "M 0 0 12 6 0 12 3 6")
    .style("fill", "black"); 

  // Store old positions for smooth transitions
  nodes.forEach(function(d){ 
    d.x0 = d.x; 
    d.y0 = d.y; 
  }); 

  // Curved path connecting parent and child right edges
  function diagonal(s, d) { 
    var parentX = o.x(d); 
    var parentY = o.y(d); 
    var childX = o.x(s); 
    var childY = o.y(s); 

    return `M ${parentX} ${parentY} 
            C ${(parentX + childX)/2} ${parentY}, 
              ${(parentX + childX)/2} ${childY}, 
              ${childX} ${childY}`; 
  } 

  // Toggle children visibility on node click
  function click(d) { 
    if (d.children) { 
      d._children = d.children; 
      d.children = null; 
    } else { 
      d.children = d._children; 
      d._children = null; 
    } 
    update(d); 
  } 
}

Quick Check of Key Fixes

  • Right-to-left flow: The root node starts at the right, and each child level moves leftward.
  • Right-side links: All connections originate from the right edge of parent nodes and connect to the right edge of child nodes.
  • Smooth transitions: Collapse/expand animations still work seamlessly with the new layout.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 06:57:34