首页 > 解决方案 > D3 管理多个图表标签的多个类

问题描述

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

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');
      }
    });
  });


}
<script src="https://d3js.org/d3.v3.min.js"></script>

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

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

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)');

问题

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

标签: javascriptd3.js

解决方案


您应该使用 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[start, value, end]迭代 3 次作为数据点。

 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 + ")")
      );
    });
*/

}


推荐阅读