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

D3.js v5从JSON创建带分组SVG:新手求代码优化方案

Fixing Your D3.js SVG Generation & Update Logic (v5.16)

Hey there! Let's work through getting your D3.js code to generate the exact SVG structure you want, and get that update function working without weird workarounds. I'll break down the problems in your current code and show you a clean, maintainable approach.

First, Let's Diagnose the Issues

Your current code has a few key problems that are causing the wonky behavior:

  1. Data Binding Confusion: When you append a rect inside the enter function, you're returning the rect element instead of the outer g—D3 needs to track the outer g (bound to your mainnodes data) to handle updates correctly.
  2. Overly Broad Selectors: In the update phase, d3.select("g") grabs all <g> elements on the page, not just the ones tied to your mainnodes data. This leads to incorrect updates.
  3. Nested Data Handling: Your approach to adding subnodes (circle/text) inside .each() is messy and doesn't properly bind the subnodes data to their elements, making updates unreliable.

The Correct Implementation

Here's a refactored version that generates your desired SVG structure and handles updates smoothly. I've added comments to explain each step:

// Your sample data
var json = {"mainnodes":[{"name":"node1","y":50,"subnodes":[{"name":"subnode1","x":100}]},{"name":"node2","y":100,"subnodes":[{"name":"subnode2","x":200}]}]}; 
var json2 = {"mainnodes":[{"name":"node1","y":50,"subnodes":[{"name":"subnode1","x":100}]},{"name":"node2","y":150,"subnodes":[{"name":"subnode2","x":200}]}]}; 

var currentData = json; 
var svgContainer = d3.select("body")
  .append("svg")
  .attr("width", 1024)
  .attr("height", 700);

function update() {
  // 1. Bind mainnodes data to outer <g> elements (gp_nodeX)
  // Use a key function (d => d.name) to ensure D3 matches elements correctly during updates
  var mainGroups = svgContainer.selectAll("g.gp-main")
    .data(currentData.mainnodes, d => d.name);

  // ------------------------------
  // ENTER: Create new elements for new data
  // ------------------------------
  var enterMainGroups = mainGroups.enter()
    .append("g")
    .attr("class", "gp-main") // Add a class for easy selection
    .attr("id", d => `gp_${d.name}`);

  // Add the rect to each new outer group
  enterMainGroups.append("rect")
    .attr("x", 0)
    .attr("y", d => d.y)
    .attr("width", 1024)
    .attr("height", 10)
    .attr("id", d => d.name);

  // Add the inner <g> (gm_nodeX) to each new outer group, then bind subnodes data
  var innerGroups = enterMainGroups.append("g")
    .attr("class", "gm-sub")
    .attr("id", d => `gm_${d.name}`)
    .selectAll(".sub-element") // Placeholder selector for subnodes
    .data(d => d.subnodes); // Bind the subnodes array of each mainnode

  // Add circle for each subnode
  innerGroups.enter()
    .append("circle")
    .attr("class", "sub-element")
    .attr("cx", d => d.x)
    .attr("cy", 200)
    .attr("r", 10)
    .attr("id", d => d.name);

  // Add text for each subnode
  innerGroups.enter()
    .append("text")
    .attr("class", "sub-element")
    .attr("x", 0)
    .attr("y", 10)
    .attr("id", d => `text${d.name}`)
    .text(d => d.name);

  // ------------------------------
  // UPDATE: Modify existing elements with new data
  // ------------------------------
  // Merge enter and update groups to avoid repeating code
  var allMainGroups = mainGroups.merge(enterMainGroups);

  // Update outer group ID (if needed)
  allMainGroups.attr("id", d => `gp_${d.name}`);

  // Update rect attributes
  allMainGroups.select("rect")
    .attr("y", d => d.y)
    .attr("id", d => d.name);

  // Update inner group and its children
  allMainGroups.select("g.gm-sub")
    .attr("id", d => `gm_${d.name}`)
    .each(function(d) {
      // Rebind subnodes data to the inner group's children
      var subData = d.subnodes;
      
      // Update circle
      d3.select(this).select("circle")
        .data(subData)
        .attr("cx", d => d.x)
        .attr("id", d => d.name);
      
      // Update text
      d3.select(this).select("text")
        .data(subData)
        .attr("id", d => `text${d.name}`)
        .text(d => d.name);
    });

  // ------------------------------
  // EXIT: Remove elements for deleted data
  // ------------------------------
  mainGroups.exit().remove();
}

// Toggle data on SVG click
d3.select('svg').on('click', function(){
  currentData = json2;
  update();
});

// Initial render
update();

Key Improvements Explained

  • Key Function in Data Binding: Using .data(..., d => d.name) tells D3 to match elements by the mainnode's name, not their position in the array. This ensures updates target the correct elements when data changes.
  • Clean Nested Element Creation: We split the creation of outer groups, rects, inner groups, and subnodes into distinct steps. This keeps data binding clear and avoids mixing parent/child data contexts.
  • Merge Enter/Update: Instead of writing separate code for enter and update, we merge the two groups to apply the same updates to both new and existing elements.
  • Targeted Selectors: Using classes like gp-main and gm-sub ensures we only select the elements we care about, not every <g> on the page.

Verifying the SVG Structure

This code will generate exactly the SVG structure you specified, with the addition of helper classes to make selection easier:

<svg width="1024" height="700">
  <g class="gp-main" id="gp_node1">
    <rect x="0" y="50" width="1024" height="10" id="node1"></rect>
    <g class="gm-sub" id="gm_node1">
      <circle class="sub-element" cx="100" cy="200" r="10" id="subnode1"></circle>
      <text class="sub-element" x="0" y="10" id="textsubnode1">subnode1</text>
    </g>
  </g>
  <!-- ... and the second node group ... -->
</svg>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 17:27:38