【问题标题】:How to create a data custom hierachy sum in D3?如何在 D3 中创建数据自定义层次结构总和?
【发布时间】:2026-02-15 02:55:01
【问题描述】:

我正在使用 D3v5 和 React。

我有这个数据

{
  "name": "main",
  "children": [
    {
      "name": "testedin",
      "children": [
        {
          "name":"positive",
          "value": 53,
          "floor":2
        },
        {
          "name":"negative",
          "value": 24,
          "floor":2
        },
        {
          "name":"unknown",
          "value": 23,
          "floor":2
        }

      ],
      "floor":1,
      "value":55
    },
    {
      "name": "Not tested",
      "value": 45,
      "floor":1
    }
  ],
  "floor":0
}

我想画这个图表,其中第二列rects的数据是第一列rect父级的百分比值。

我遵循了这个例子:icecle Chart,一切正常。

问题在于,由于这段代码,图表中的值只是总结起来:

const partition = data => {
      const root = d3.hierarchy(data)
          .sum(d => d.value)
          .sort((a, b) => b.height - a.height || b.value - a.value);  
      return d3.partition()
          .size([height, (root.height + 1) * width / 4])
        (root);
    }

.sum 函数只是将每个孩子的值相加。 有这个结果:

我尝试更改 sum 函数,以便对值有不同的用法,但找不到编辑它的方法,以便获得百分比。

【问题讨论】:

    标签: reactjs typescript d3.js charts hierarchy


    【解决方案1】:

    Sum 在这里不起作用。相反,我们可以创建层次结构,然后遍历树并计算代表每个节点/条的高度的标准化值:

      root.value = 1;
      root.eachBefore(d=>{
        if(d.parent) d.value = d.data.value * d.parent.value / 100;
      })
    

    我们将根的总值设置为 1。在使用 root.eachBefore 我们访问节点“这样一个给定的节点只有在它的所有祖先都已经被访问过之后才会被访问(docs)”。每当我们访问一个节点时,我们都会相对于它的父节点缩放它的值:如果一个节点代表 55%,它的大小值将是其父节点的 55%。现在我们有了一组具有 value 属性的节点,该属性表示给定节点与根相比的相对高度,我们可以将其传递给分区的标准化值。

    在缩放子节点时,父节点的缩放值 (d.value) 与子节点的未缩放值 (d.data.value) 一起使用。我还使用下面的 d.data.value 来访问用于标记的原始百分比(因为原始数据保存在 d.data 中)。

    const width = 500;
    const height = 150;
    const color = d3.scaleOrdinal()
      .range(d3.schemeCategory10)
    
    const data = {
      "name": "main",
      "children": [
        {
          "name": "testedin",
          "children": [
            {
              "name":"positive",
              "value": 53,
              "floor":2
            },
            {
              "name":"negative",
              "value": 24,
              "floor":2
            },
            {
              "name":"unknown",
              "value": 23,
              "floor":2
            }
    
          ],
          "floor":1,
          "value":55
        },
        {
          "name": "Not tested",
          "value": 45,
          "floor":1
        }
      ],
      "floor":0
    }
    
    const partition = data => {
          const root = d3.hierarchy(data)
              .sort((a, b) => b.height - a.height || b.value - a.value); 
              
          
          root.value = 1;
          root.eachBefore(d=>{
            if(d.parent) d.value = d.data.value * d.parent.value / 100;
          })
          
          // or, slightly more compactly:
          // root.eachBefore(d=>{d.value = d.parent ? d.data.value * d.parent.value / 100 : 1})
              
              
          return d3.partition()
              .size([height, (root.height + 1) * width / 4])
            (root);
        }
        
    const root = partition(data);
    
    
    
    const svg = d3.select("body").append("svg")
          .attr("viewBox", [0, 0, width, height])
          .style("font", "10px sans-serif");
    
      const cell = svg
        .selectAll("g")
        .data(root.descendants())
        .join("g")
          .attr("transform", d => `translate(${d.y0},${d.x0})`);
    
      cell.append("rect")
          .attr("width", d => d.y1 - d.y0)
          .attr("height", d => d.x1 - d.x0)
          .attr("fill-opacity", 0.6)
          .attr("fill", d => {
            if (!d.depth) return "#ccc";
            while (d.depth > 1) d = d.parent;
            return color(d.data.name);
          });
    
      const text = cell.filter(d => (d.x1 - d.x0) > 16).append("text")
          .attr("x", 4)
          .attr("y", 13);
    
      text.append("tspan")
          .text(d => d.data.name);
    
      text.append("tspan")
          .attr("fill-opacity", 0.7)
          .text(d =>Math.round(d.data.value) || "")
          .attr("dx", 10);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>

    【讨论】:

    • 谢谢,它看起来很完美。但是在我的打字稿方面尝试实现它时我有一个小问题:在分配数据时 root.value = 1;或 d.value = d.data.value * d.parent.value / 100 它给了我这个错误:“不能分配给 'value' 因为它是一个只读属性。ts(2540) (property) HierarchyNode.value?: 任何由 sum(value) 或 count() 计算的聚合数值,如果之前调用过。"你知道为什么会这样吗?
    • 嗯,我想有 ways 来覆盖只读指定 - 但我的打字稿知识并不是很好知道它们是否适合而不实际测试它。我今天可能会尝试设置一个工作示例,时间依赖。