D3 Donut Chart with Connectors

2019-06-04 23:54发布

I'm trying to create a static d3 donut chart with labels and connectors from a json object. I've been able to get it to work with an array in this fiddle but can't get the connectors or label text to appear with the data object that I need.

The donut chart is working and the labels are appearing with the percentages, but I need them to appear with the labels and connectors. I think that it has something to do with the way that I am trying to map the connectors but can't figure out the error.

Code is below and also here is a link to a working fiddle: https://jsfiddle.net/hef1u71o/

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>


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

var data = [{
    percentage: 19,
    label: 'Consulting'
  },{
    percentage: 3,
    label: 'Consumer Goods'
  },{
    percentage: 5,
    label: 'Energy/Chemical/Gas'
  },{
    percentage: 3,
    label: 'Entrepreneurship'
  },{
    percentage: 1,
    label: 'Environment & Sustainability'
  },{
    percentage: 19,
    label: 'Financial Services'
  },{
    percentage: 3,
    label: 'General Management'
  },{
    percentage: 6,
    label: 'Government'
  },{
    percentage: 7,
    label: 'Hospital/Health Care/Health Services'
  },{
    percentage: 2,
    label: 'Human Resources'
  },{
    percentage: 4,
    label: 'IT'
  },{
    percentage: 2,
    label: 'International Development'
  },{
    percentage: 3,
    label: 'Manufacturing/Operations'
  },{
    percentage: 4,
    label: 'Marketing/PR/Advertising'
  },{
    percentage: 1,
    label: 'Media/Sports/Entertainment'
  },{
    percentage: 7,
    label: 'Nonprofit/Education/Special Org.'
  },{
    percentage: 6,
    label: 'Other'
  },{
    percentage: 2,
    label: 'Research & Development'
  },{
    percentage: 4,
    label: 'Sales/Business Development'
  },];


var width = 300,
    height = 300,
    radius = Math.min(width, height) / 2;

var color = d3.scale.ordinal()
  .range(["#243668", "#2b7eb4", "#186b97", "#6391a1", "#d2c5b7", "#9c9286", "#5b5b59"]);


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


var arc = d3.svg.arc()
    .innerRadius(radius - 100)
    .outerRadius(radius - 50);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
    .append("g")
    .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

var path = svg.selectAll("path")
.data(pie(data))
    .enter().append("path")
    .attr("fill", function(d, i) { return color(i); })
    .attr("d", arc);

svg.selectAll("text").data(pie(data))
    .enter()
    .append("text")
    .attr("text-anchor", "middle")
    .attr("x", function(d) {
        var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
        d.cx = Math.cos(a) * (radius - 75);
        return d.x = Math.cos(a) * (radius - 20);
    })
    .attr("y", function(d) {
        var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
        d.cy = Math.sin(a) * (radius - 75);
        return d.y = Math.sin(a) * (radius - 20);
    })
    .text(function(d) { return d.value; })
    .each(function(d) {
        var bbox = this.getBBox();
        d.sx = d.x - bbox.width/2 - 2;
        d.ox = d.x + bbox.width/2 + 2;
        d.sy = d.oy = d.y + 5;
    });

svg.append("defs").append("marker")
    .attr("id", "circ")
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("refX", 3)
    .attr("refY", 3)
    .append("circle")
    .attr("cx", 3)
    .attr("cy", 3)
    .attr("r", 3);

svg.selectAll("path.pointer").data(pie(data)).enter()
    .append("path")
    .attr("class", "pointer")
    .style("fill", "none")
    .style("stroke", "black")
    .attr("marker-end", "url(#circ)")
    .attr("d", function(d) {
        if(d.cx > d.ox) {
            return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
        } else {
            return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
        }
    });


</script>
  </body>
</html>

1条回答
趁早两清
2楼-- · 2019-06-05 00:26

Save your data into variable:

var pieData = pie(data);

And use this variable here:

svg.selectAll("text").data(pieData)
    .enter()
    .append("text")
    .attr("text-anchor", "middle")
    .attr("x", function(d) {
        var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
        d.cx = Math.cos(a) * (radius - 75);
        return d.x = Math.cos(a) * (radius - 20);
    })
    .attr("y", function(d) {
        var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
        d.cy = Math.sin(a) * (radius - 75);
        return d.y = Math.sin(a) * (radius - 20);
    })
    .text(function(d) { return d.value; })
    .each(function(d) { // !!! you extent the dataset here 
        var bbox = this.getBBox();
        d.sx = d.x - bbox.width/2 - 2;
        d.ox = d.x + bbox.width/2 + 2;
        d.sy = d.oy = d.y + 5;
    });

and here:

svg.selectAll("path.pointer").data(pieData).enter()
    .append("path")
    .attr("class", "pointer")
...

It's important because of you extend the data (see each method). You will use extended properties for calculating of connectors position and you should use the same dataset for both cases.

Check working demo.

查看更多
登录 后发表回答