【问题标题】:D3 managing multiple classes for multiple chart labelsD3 管理多个图表标签的多个类
【发布时间】:2020-01-11 22:51:59
【问题描述】:

在一页上编译多个圆环图时,我注意到在附加数据标签时我得到了一些“工件”。以下片段:

var margins = {
  top: 20,
  left: 50,
  bottom: 20,
  right: 20
};

var width = 300;
var arcSize = (6 * width / 100);
var innerRadius = arcSize * 3;

var extent = 1800;

var data2 = [
  [{
      value: (231 / extent * 100),
      marker: 231,
      label: "Collective",
      color: '#b8cce4',
      neg: false
    },
    {
      value: (233 / extent * 100),
      marker: 233,
      label: "Targeted",
      color: '#95b3d7',
      neg: false
    },
    {
      value: (45 / extent * 100),
      marker: 45,
      label: "Specific",
      color: '#4f81b9',
      neg: false
    },
  ],

  [{
      value: (171 / extent * 100),
      marker: 171,
      label: "Collective",
      color: '#b8cce4',
      neg: false
    },
    {
      value: (1712 / extent * 100),
      marker: 1712,
      label: "Targeted",
      color: '#95b3d7',
      neg: false
    },
    {
      value: (1 / extent * 100),
      marker: 1,
      label: "Specific",
      color: '#4f81b9',
      neg: false
    },
  ],

  [{
      value: (207 / extent * 100),
      marker: 207,
      label: "Collective",
      color: '#b8cce4',
      neg: false
    },
    {
      value: (975 / extent * 100),
      marker: 975,
      label: "Targeted",
      color: '#95b3d7',
      neg: false
    },
    {
      value: (153 / extent * 100),
      marker: 153,
      label: "Specific",
      color: '#4f81b9',
      neg: false
    },
  ]


];


var svg = d3.select('body').append('svg').attr('width', 1100 + 100).attr('height', 1100 + 100);

var graphGroup = svg.append("g")
  .attr("transform", "translate(" + margins.left + "," + margins.top + ")");

for (var j = 0; j < (data2.length); j++) {

  var data = data2[j];

  var arcs = data.map(function(obj, i) {
    return d3.svg.arc().innerRadius(i * arcSize + innerRadius).outerRadius((i + 1) * arcSize - (width / 100) + innerRadius);
  });
  var arcsGrey = data.map(function(obj, i) {
    return d3.svg.arc().innerRadius(i * arcSize + (innerRadius + ((arcSize / 2) - 2))).outerRadius((i + 1) * arcSize - ((arcSize / 2)) + (innerRadius));
  });

  var pieData = data.map(function(obj, i) {
    return [{
        value: obj.value * 0.75,
        arc: arcs[i],
        object: obj
      },
      {
        value: (100 - obj.value) * 0.75,
        arc: arcsGrey[i],
        object: obj
      },
      {
        value: 100 * 0.25,
        arc: arcs[i],
        object: obj
      }
    ];
  });

  var pie = d3.layout.pie().sort(null).value(function(d) {
    return d.value;
  });

  var g = graphGroup.selectAll(null).data(pieData).enter()
    .append('g')
    .attr('transform', 'translate(' + (width / 2 + (j * 300)) + ',' + width / 2  + ') rotate(180)');

var thisClass = "g"+String(j);

  var gText = graphGroup.selectAll('.'+thisClass).data([{}]).enter()
    .append('g')
    .classed('textClass', true)
    .attr('transform', 'translate(' + (width / 2 + (j * 300)) + ',' + width / 2 + ') rotate(180)');




  g.selectAll('path').data(function(d) {
      return pie(d);
    }).enter().append('path')
    .attr('id', function(d, i) {
      if (i == 1) {
        return "Text" + d.data.object.label
      }
    })
    .attr('d', function(d) {
      return d.data.arc(d);
    }).attr('fill', function(d, i) {
      if (d.data.object.neg == false) {
        return i == 0 ? d.data.object.color : i == 1 ? '#D3D3D3' : 'none';
      } else {
        return i == 0 ? 'red' : i == 1 ? '#D3D3D3' : 'none';
      }
    }).attr('class', 'segments');

  /*
  g.selectAll('.segments').attr('fill', function(d,i) {
    return d.data.object.neg==true ? 'red' : 'none';
  });
  */

  graphGroup.selectAll('g').each(function(d, index) {
    var el = d3.select(this);
    var path = el.selectAll('path').each(function(r, i) {
      if (i === 1) {
        var centroid = r.data.arc.centroid({
          startAngle: r.startAngle + 0.05,
          endAngle: r.startAngle + 0.001 + 0.05
        });
        var lableObj = r.data.object;
        var thisLength = this.getTotalLength();
        g.append('text')
          .attr('font-size', ((5 * width) / 100))
          .attr('dominant-baseline', 'central')
          /*.attr('transform', "translate(" + centroid[0] + "," + (centroid[1] + 10) + ") rotate(" + (180 / Math.PI * r.startAngle + 7) + ")")
           .attr('alignment-baseline', 'middle')*/
          .append("textPath")
          .attr("textLength", function(d, i) {
            return 0;
          })
          .attr("xlink:href", "#Text" + r.data.object.label)
          .attr("startOffset", function() {
            return thisLength-22;
            //return index === 2 || index === 5 ? thisLength - 22 : 5
          })
          .attr('font-weight', 'normal')
          .attr("dy", '-3em')
          .text(function(d) {
            if (lableObj.neg == true) {
              return '-' + lableObj.marker;
            } else {
              return lableObj.marker;
            }
          });
      }
      if (i === 0) {
        var centroidText = r.data.arc.centroid({
          startAngle: r.startAngle,
          endAngle: r.startAngle
        });
        var lableObj = r.data.object;
        gText.append('text')
          .attr('font-size', ((5 * width) / 100))
          .text(lableObj.label)
          .attr('transform', "translate(" + (centroidText[0] - ((1.5 * width) / 100)) + "," + (centroidText[1] + ") rotate(" + (180) + ")"))
          .attr('dominant-baseline', 'central');
      }
    });
  });


}
&lt;script src="https://d3js.org/d3.v3.min.js"&gt;&lt;/script&gt;

在使用.selectAll(null) 显示多个甜甜圈图的一些帮助之后,我一直在徒劳地从第二张和第三张图表中删除文本“artifacts”。出于某种原因,数据标签会重复,从 sn-p 中可以看出。

我尝试的是创建一个对当前迭代唯一的单独类:

var thisClass = "g"+String(j);

  var gText = graphGroup.selectAll('.'+thisClass).data([{}]).enter()
    .append('g')
    .classed('textClass', true)
    .attr('transform', 'translate(' + (width / 2 + (j * 300)) + ',' + width / 2 + ') rotate(180)');

问题

为什么我的基于类的解决方案没有按预期工作?我对仅显示正确数据标签的其他建议持开放态度(似乎没有保留轮换,不知道为什么)。

【问题讨论】:

    标签: javascript d3.js


    【解决方案1】:

    您应该使用 d3 内部数据迭代,而不是自己迭代选择。

    您的问题的解决方案是为您可以迭代的每个图表创建一个组元素:

     const arcGroup = g
        .selectAll(".group-arc")
        .data(d => pie(d))
        .enter()
        .append("g")
        .attr("class", "group-arc");
    

    这将允许您根据pie(d) 数组中的数据对象数量将元素附加到您的组中,在您的情况下: 3. 之后,对于数据组中的每个元素,您可以附加路径(这将在组中添加一个path 孩子。)。 pie(d) 的结果将是每个图表的 3 个数据点 [start, value, end] 的数组 - 这将导致在 g 元素上迭代 3 次,每个 .group-arc 迭代 3 次以上 [ start, value, end] 作为数据点。

     arcGroup
        .append("path")
        .attr("id", function(d, i) {
          if (i == 1) {
            return "Text" + d.data.object.label;
          }
        })
        .attr("d", function(d) {
          return d.data.arc(d);
        })
        .attr("fill", function(d, i) {
          if (d.data.object.neg == false) {
            return i == 0 ? d.data.object.color : i == 1 ? "#D3D3D3" : "none";
          } else {
            return i == 0 ? "red" : i == 1 ? "#D3D3D3" : "none";
          }
        })
    

    下一步是添加文本属性。每个数据对象d 将是一个包含 3 个饼元素的数组:开始、值、结束。您可以使用i 索引来了解您当前正在呈现哪个标签(0 - 饼图标签,1 - 值)。可以使用相同的索引来计算旋转。

    arcGroup
        .append("text")
        .text(function(d, i) {
          var lableObj = d.data.object;
          if (i === 0) {
            return lableObj.label;
          } else if (i === 1) {
            if (lableObj.neg === true) {
              return "-" + lableObj.marker;
            } else {
              return lableObj.marker;
            }
          }
        })
        .attr("transform", (d, i) => {
          var centroidText = d.data.arc.centroid({
            startAngle: d.startAngle,
            endAngle: d.startAngle
          });
          return (
            "translate(" +
            (centroidText[0] - (1.5 * width) / 100) +
            "," +
            (centroidText[1] + ") rotate(" + 180 + ")")
          );
        });
    

    或者您可以使用.call 方法在单个路径组上执行代码:

     arcGroup.call(elem => {
        elem.forEach(arcGr => {
          const { parentNode } = arcGr;
          const groupAdd = d3.select(parentNode);
          const arcData = d3.select(arcGr[0]).data();
    
          groupAdd
            .append("text")
            .attr("alignment-baseline", "middle")
            .text(d => d[0].object.label)
            .attr("transform", d => {
              var centroidText = arcData[0].data.arc.centroid({
                startAngle: arcData[0].startAngle,
                endAngle: arcData[0].startAngle
              });
              console.log(centroidText);
              return `translate(${centroidText.join(",")})rotate(180)`;
            });
    
          groupAdd
            .append("text")
            .text(d => d[1].object.marker)
            .attr("alignment-baseline", "hanging")
            .attr("text-anchor", "end")
            .attr("dx", "-2")
            .attr("transform", d => {
              var path = arcData[0].data.arc(arcData[0]);
              var coords = path.split("L")[1].split("A")[0];
              return `translate(${coords})rotate(180)rotate(${(arcData[0].endAngle *
                180) /
                Math.PI})`;
            });
        });
      });
    

    上面的实现可能不是确切的期望输出,但我希望你知道如何简化你的实现。我还注意到您正在使用 d3 v3 - 如果可能,您应该切换到 v5。

    完整代码如下:

    
    var margins = {
      top: 20,
      left: 50,
      bottom: 20,
      right: 20
    };
    
    var width = 300;
    var arcSize = (6 * width) / 100;
    var innerRadius = arcSize * 3;
    
    var extent = 1800;
    
    var data2 = [
      [
        {
          value: (231 / extent) * 100,
          marker: 231,
          label: "Collective",
          color: "#b8cce4",
          neg: false
        },
        {
          value: (233 / extent) * 100,
          marker: 233,
          label: "Targeted",
          color: "#95b3d7",
          neg: false
        },
        {
          value: (45 / extent) * 100,
          marker: 45,
          label: "Specific",
          color: "#4f81b9",
          neg: false
        }
      ],
    
      [
        {
          value: (171 / extent) * 100,
          marker: 171,
          label: "Collective",
          color: "#b8cce4",
          neg: false
        },
        {
          value: (1712 / extent) * 100,
          marker: 1712,
          label: "Targeted",
          color: "#95b3d7",
          neg: false
        },
        {
          value: (1 / extent) * 100,
          marker: 1,
          label: "Specific",
          color: "#4f81b9",
          neg: false
        }
      ],
    
      [
        {
          value: (207 / extent) * 100,
          marker: 207,
          label: "Collective",
          color: "#b8cce4",
          neg: false
        },
        {
          value: (975 / extent) * 100,
          marker: 975,
          label: "Targeted",
          color: "#95b3d7",
          neg: false
        },
        {
          value: (153 / extent) * 100,
          marker: 153,
          label: "Specific",
          color: "#4f81b9",
          neg: false
        }
      ]
    ];
    
    var svg = d3
      .select("body")
      .append("svg")
      .attr("width", 1100 + 100)
      .attr("height", 1100 + 100);
    
    var graphGroup = svg
      .append("g")
      .attr("transform", "translate(" + margins.left + "," + margins.top + ")");
    
    for (var j = 0; j < data2.length; j++) {
      var data = data2[j];
    
      var arcs = data.map(function(obj, i) {
        return d3.svg
          .arc()
          .innerRadius(i * arcSize + innerRadius)
          .outerRadius((i + 1) * arcSize - width / 100 + innerRadius);
      });
      var arcsGrey = data.map(function(obj, i) {
        return d3.svg
          .arc()
          .innerRadius(i * arcSize + (innerRadius + (arcSize / 2 - 2)))
          .outerRadius((i + 1) * arcSize - arcSize / 2 + innerRadius);
      });
    
      var pieData = data.map(function(obj, i) {
        return [
          {
            value: obj.value * 0.75,
            arc: arcs[i],
            object: obj
          },
          {
            value: (100 - obj.value) * 0.75,
            arc: arcsGrey[i],
            object: obj
          },
          {
            value: 100 * 0.25,
            arc: arcs[i],
            object: obj
          }
        ];
      });
    
      var pie = d3.layout
        .pie()
        .sort(null)
        .value(function(d) {
          return d.value;
        });
    
      var g = graphGroup
        .selectAll(null)
        .data(pieData)
        .enter()
        .append("g")
        .attr(
          "transform",
          "translate(" + (width / 2 + j * 300) + "," + width / 2 + ") rotate(180)"
        );
    
      const arcGroup = g
        .selectAll(".group-arc")
        .data(d => pie(d))
        .enter()
        .append("g")
        .attr("class", "group-arc");
    
      arcGroup
        .append("path")
        .attr("id", function(d, i) {
          if (i == 1) {
            return "Text" + d.data.object.label;
          }
        })
        .attr("d", function(d) {
          return d.data.arc(d);
        })
        .attr("fill", function(d, i) {
          if (d.data.object.neg == false) {
            return i == 0 ? d.data.object.color : i == 1 ? "#D3D3D3" : "none";
          } else {
            return i == 0 ? "red" : i == 1 ? "#D3D3D3" : "none";
          }
        });
    
     arcGroup.call(elem => {
        elem.forEach(arcGr => {
          const { parentNode } = arcGr;
          const groupAdd = d3.select(parentNode);
          const arcData = d3.select(arcGr[0]).data();
    
          groupAdd
            .append("text")
            .attr("alignment-baseline", "middle")
            .text(d => d[0].object.label)
            .attr("transform", d => {
              var centroidText = arcData[0].data.arc.centroid({
                startAngle: arcData[0].startAngle,
                endAngle: arcData[0].startAngle
              });
              console.log(centroidText);
              return `translate(${centroidText.join(",")})rotate(180)`;
            });
    
          groupAdd
            .append("text")
            .text(d => d[1].object.marker)
            .attr("alignment-baseline", "hanging")
            .attr("text-anchor", "end")
            .attr("dx", "-2")
            .attr("transform", d => {
              var path = arcData[0].data.arc(arcData[0]);
              var coords = path.split("L")[1].split("A")[0];
              return `translate(${coords})rotate(180)rotate(${(arcData[0].endAngle *
                180) /
                Math.PI})`;
            });
        });
      });
    
    
    /** 
      //Previous version
    
      arcGroup
        .append("text")
        .text(function(d, i) {
          var lableObj = d.data.object;
          if (i === 0) {
            return lableObj.label;
          } else if (i === 1) {
            if (lableObj.neg === true) {
              return "-" + lableObj.marker;
            } else {
              return lableObj.marker;
            }
          }
        })
        .attr("transform", (d, i) => {
          var centroidText = d.data.arc.centroid({
            startAngle: d.startAngle,
            endAngle: d.startAngle
          });
          return (
            "translate(" +
            (centroidText[0] - (1.5 * width) / 100) +
            "," +
            (centroidText[1] + ") rotate(" + 180 + ")")
          );
        });
    */
    
    }
    
    

    【讨论】:

    • 谢谢,这很有帮助。现在唯一的问题是在您的实现中似乎没有保留文本旋转。关于为什么会这样的任何想法?我不太清楚为什么旋转变换不起作用。
    • 使用.call 使用变体更新了示例。
    猜你喜欢
    • 2014-08-17
    • 2017-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-26
    相关资源
    最近更新 更多