将甜甜圈图修改为“进步甜甜圈图”

时间:2019-04-01 16:26:10

标签: javascript d3.js

我正在尝试开发一个d3甜甜圈图视觉效果,该视觉效果仅需要用户定义的百分比-介于0到100%之间。根据这个值,我希望甜甜圈的段数等于比例。例如,如果50%,则将附加甜甜圈图段的一半。同样,如果100%,则将绘制整个甜甜圈图;所有段都将被追加。我不知道如何以一种优雅的方式实现这一目标,但是我确实找到了一个粗略的解决方法,您可以在下面的代码段中看到。

var data =
[{'value':0,'interval':6.25},
{'value':6.25,'interval':6.25},
{'value':12.5,'interval':6.25},
{'value':18.75,'interval':6.25},
{'value':25,'interval':6.25},
{'value':31.25,'interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25},
{'value':'none','interval':6.25}];


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

var color = d3.scale.linear()
        .range(["#0005fd","#00fe80"]).domain([0,35]);

var explode = function(x,index) {
  var offset = (index==5) ? 80:0;
  var angle = (x.startAngle + x.endAngle)/2;
  var xOff = Math.sin(angle)*offset;
  var yOff = -Math.cos(angle)*offset;
  return "translate(" +xOff+","+yOff+ ")";
}

var arc = d3.svg.arc()
    .outerRadius(radius - 10)
    .innerRadius(radius - 70);

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

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

  var g = svg.selectAll(".arc")
      .data(pie(data))
    .enter().append("g")
      .attr("class", "arc");

  g.append("path")
      .attr("d", arc)
      //.attr('transform', explode)
      .style('stroke','none')
      .style("fill", function(d) {
        if (d.data.value=='none') {
          return 'none'
        }
        return color(d.data.value); });

  g.append("text")
      .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
      .attr("dy", ".35em")
      //.text(function(d) { return d.data.age; });

svg.append('text')
    .text('37.5%') // magic number
    .attr('font-family','Play')
    .attr('font-size','140px')
    .attr('text-anchor','middle')
    .attr('x',0)
    .attr('y',40);


function type(d) {
  d.interval = +d.interval;
  return d;
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>

</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>

从本质上讲,正在发生的事情是带有值的条目与读为“无”的条目的比例为37.5%,这是我的魔幻数字(6个值和10个无,因此6/16 = 37.5%)。不用说这根本不可扩展。

问题

在我的特定时刻是否有内置的方法或其他劳动强度较小的解决方案?我只希望能够将0到100之间的数字传递给函数,然后绘制出该甜甜圈部分的百分比。在我的特定情况下,我选择了6.25,因为它在美学上似乎是最令人愉悦的。

也许用透明的自定义填充来模拟具有爆炸段的效果?似乎太hacky ...

注意:版本是可选的。那就是说我不反对d3.v5解决方案,我只是使用d3.v3,因为我还没有在v5中使用甜甜圈。

1 个答案:

答案 0 :(得分:1)

最简单的方法是在其余线段的顶部添加另一个弧。该弧可以是任意长度,因此它覆盖了所有不需要显示的段。这可以通过以下方式完成:

  var percentage = .35;
  g.append("path")
    .attr("d", d3.svg.arc()
      .endAngle(Math.PI*2)
      .startAngle(percentage * Math.PI*2)
      .outerRadius(radius - 10)
      .innerRadius(radius - 70)
     )
     .attr("fill","white")

我们从结束角Math.PI * 2开始,这是一整圈,表示100%完成。然后,我们以较小的起始角度向后移动,覆盖了100%到我们所拥有的百分比之间的所有区域。

根据您想要的样式,甚至可以使它稍微透明以显示不完整的部分。

这是一个例子:

var data = d3.range(16).map(function(d) {
  return { value: d*6.25, interval: 6.25 };
})

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

var color = d3.scale.linear()
        .range(["#0005fd","#00fe80"]).domain([0,35]);

var explode = function(x,index) {
  var offset = (index==5) ? 80:0;
  var angle = (x.startAngle + x.endAngle)/2;
  var xOff = Math.sin(angle)*offset;
  var yOff = -Math.cos(angle)*offset;
  return "translate(" +xOff+","+yOff+ ")";
}

var arc = d3.svg.arc()
    .outerRadius(radius - 10)
    .innerRadius(radius - 70);

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

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

  var g = svg.selectAll(".arc")
      .data(pie(data))
    .enter().append("g")
      .attr("class", "arc");

  g.append("path")
      .attr("d", arc)
      //.attr('transform', explode)
      .style('stroke','none')
      .style("fill", function(d) {return color(d.data.value); });
      
      
  g.append("text")
      .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
      .attr("dy", ".35em")
      .text(function(d) { return d.data.age; });
      
  // Extra arc:
  var percentage = .35;
  g.append("path")
    .attr("d", d3.svg.arc()
      .endAngle(Math.PI*2)
      .startAngle(percentage * Math.PI*2)
      .outerRadius(radius - 10)
      .innerRadius(radius - 70)
     )
     .attr("fill","white")


svg.append('text')
    .text(percentage * 100 + "%") // magic number
    .attr('font-family','Play')
    .attr('font-size','140px')
    .attr('text-anchor','middle')
    .attr('x',0)
    .attr('y',40);


function type(d) {
  d.interval = +d.interval;
  return d;
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>

</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>

这种方法还可以轻松制作动画:

var data = d3.range(16).map(function(d) {
  return { value: d*6.25, interval: 6.25 };
})

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

var color = d3.scale.linear()
        .range(["#0005fd","#00fe80"]).domain([0,35]);

var explode = function(x,index) {
  var offset = (index==5) ? 80:0;
  var angle = (x.startAngle + x.endAngle)/2;
  var xOff = Math.sin(angle)*offset;
  var yOff = -Math.cos(angle)*offset;
  return "translate(" +xOff+","+yOff+ ")";
}

var arc = d3.svg.arc()
    .outerRadius(radius - 10)
    .innerRadius(radius - 70);

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

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

  var g = svg.selectAll(".arc")
      .data(pie(data))
    .enter().append("g")
      .attr("class", "arc");

  g.append("path")
      .attr("d", arc)
      //.attr('transform', explode)
      .style('stroke','none')
      .style("fill", function(d) {return color(d.data.value); });
      
      
  g.append("text")
      .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
      .attr("dy", ".35em")
      .text(function(d) { return d.data.age; });
      
  // Extra arc:
  var percentage = .35;

  var coverArc = g.append("path")
     .attr("fill","white")


var label = svg.append('text')
    .text(percentage * 100 + "%") // magic number
    .attr('font-family','Play')
    .attr('font-size','140px')
    .attr('text-anchor','middle')
    .attr('x',0)
    .attr('y',40);
    
function transition() {

  coverArc
    .transition()
    .tween("d", function(d) {
       var that = d3.select(this),
       i = d3.interpolateNumber(0, percentage);
       return function(t) { 
         that.attr("d", d3.svg.arc()
           .endAngle(Math.PI*2)
           .startAngle(i(t) * Math.PI*2)
           .outerRadius(radius - 10)
           .innerRadius(radius - 70)
           )
          label.text(Math.round(i(t) * 100) + "%");
        };
     })
     .duration(1000)
     // other way:
    .transition()
    .tween("d", function(d) {
       var that = d3.select(this),
       i = d3.interpolateNumber(percentage, 0);
       return function(t) { 
         that.attr("d", d3.svg.arc()
           .endAngle(Math.PI*2)
           .startAngle(i(t) * Math.PI*2)
           .outerRadius(radius - 10)
           .innerRadius(radius - 70)
           )
          label.text(Math.round(i(t) * 100) + "%");
        };
     })
     .duration(1000)
     .each("end",transition);

}

transition();




function type(d) {
  d.interval = +d.interval;
  return d;
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>

</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>

答案使用d3v3,d3v4 +有一些细微的变化,特别是与弧有关,d3.svg.arc现在是d3.arc,d3.layout.pie现在是d3.pie,并且d3.scale .linear现在是d3.scaleLinear,对于第二个片段中使用的动画,transition.each现在都是transition.on:

var data = d3.range(16).map(function(d) {
  return { value: d*6.25, interval: 6.25 };
})

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

var color = d3.scaleLinear()
        .range(["#0005fd","#00fe80"]).domain([0,35]);

var explode = function(x,index) {
  var offset = (index==5) ? 80:0;
  var angle = (x.startAngle + x.endAngle)/2;
  var xOff = Math.sin(angle)*offset;
  var yOff = -Math.cos(angle)*offset;
  return "translate(" +xOff+","+yOff+ ")";
}

var arc = d3.arc()
    .outerRadius(radius - 10)
    .innerRadius(radius - 70);

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

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

  var g = svg.selectAll(".arc")
      .data(pie(data))
    .enter().append("g")
      .attr("class", "arc");

  g.append("path")
      .attr("d", arc)
      //.attr('transform', explode)
      .style('stroke','none')
      .style("fill", function(d) {return color(d.data.value); });
      
      
  g.append("text")
      .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
      .attr("dy", ".35em")
      .text(function(d) { return d.data.age; });
      
  // Extra arc:
  var percentage = .35;

  var coverArc = g.append("path")
     .attr("fill","white")


var label = svg.append('text')
    .text(percentage * 100 + "%") // magic number
    .attr('font-family','Play')
    .attr('font-size','140px')
    .attr('text-anchor','middle')
    .attr('x',0)
    .attr('y',40);
    
function transition() {

  coverArc
    .transition()
    .tween("d", function(d) {
       var that = d3.select(this),
       i = d3.interpolateNumber(0, percentage);
       return function(t) { 
         that.attr("d", d3.arc()
           .endAngle(Math.PI*2)
           .startAngle(i(t) * Math.PI*2)
           .outerRadius(radius - 10)
           .innerRadius(radius - 70)
           )
          label.text(Math.round(i(t) * 100) + "%");
        };
     })
     .duration(1000)
     // other way:
    .transition()
    .tween("d", function(d) {
       var that = d3.select(this),
       i = d3.interpolateNumber(percentage, 0);
       return function(t) { 
         that.attr("d", d3.arc()
           .endAngle(Math.PI*2)
           .startAngle(i(t) * Math.PI*2)
           .outerRadius(radius - 10)
           .innerRadius(radius - 70)
           )
          label.text(Math.round(i(t) * 100) + "%");
        };
     })
     .duration(1000)
     .on("end",transition);

}

transition();




function type(d) {
  d.interval = +d.interval;
  return d;
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>

</style>
<body>
<script src="http://d3js.org/d3.v5.min.js"></script>