如何在D3.js树形图中实现根节点从右向左布局并将连接线移至右侧
Solution for Right-to-Left Tree Layout with Right-Side Links
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
yvalues so deeper nodes move left (instead of right) relative to the root.
2. Shift Links to Node Right Sides
- 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-endand 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
相关产品推荐
相关产品推荐

