javascript - 将圆环图修改为 "progress donut chart"

标签 javascript d3.js

我正在尝试开发一个 d3 圆环图视觉效果,它只需要用户定义的百分比——从 0 到 100% 的任意值。根据这个值,我希望 donut 的分段等于比例。例如,如果是 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 之间的数字传递给函数,然后绘制出该百分比的 donut 段。在我的特定情况下,我选择了 6.25,因为它看起来最美观。

也许自定义填充透明度来模拟爆炸段的效果?看起来太老套了...

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

最佳答案

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

  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")

我们从结束 Angular Math.PI*2 开始,它是一整圈,代表 100% 完成。然后我们以较小的起始 Angular 向后移动,覆盖 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>

关于javascript - 将圆环图修改为 "progress donut chart",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55459625/

相关文章:

javascript - 刷新后使用 Javascript 元素的位置

javascript - 如何在到达屏幕一侧后反转移动 div 的方向?

javascript - D3 缩放 - 无法读取未定义的属性 'apply'

d3.js - 如何删除组元素

d3.js - D3.js 中的边界框

javascript - 如何旋转 Canvas 中的图像?

javascript - 根据条件设置节点的颜色

javascript - 无法在 Node js中安装依赖项

javascript - 尽管包含下划线,但表达式不适用于下划线

javascript - 构造函数或数组或 for 循环中的错误?