代码之家  ›  专栏  ›  技术社区  ›  The Old County

d3js v4-J环箍图-带弧形的长杆

  •  0
  • The Old County  · 技术社区  · 4 年前

    我正在开发一个d3应用程序,它有一个环形图表。我有一个版本,这是一个径向360型图表-但我不确定你会如何配置路径,以这种方式弧。

    //一张新月形的图表 http://jsfiddle.net/2wfktc3g/

    var $this = $('.crescentchart');
    
    var data = [{
        label: 'Fudge Brownie',
        value: 5,
      },
      {
        label: 'Cherry Vanilla',
        value: 60,
      },
      {
        label: 'Pistachio',
        value: 5,
      },
      {
        label: 'Caramel',
        value: 10,
      }
    ];
    
    var oldData = "";
    
    var width = $this.data('width'),
      height = $this.data('height'),
      radius = $this.data('r'),
      thickness = $this.data("thickness"),
      spacing = $this.data("spacing");
    
    var color = d3.scaleOrdinal()
      .range(["#bc658d", "#82c4c3", "#f9d89c", "#f5a7a7"]);
    
    var svg = d3.select($this[0])
      .append("svg")
      .attr("width", width)
      .attr("height", height)
      .append("g")
      .attr('class', 'crescentchart')
      .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
    
    var segments = svg.append('g').attr('class', 'segments');
    
    var data = setData(data, radius);
    
    //append previous value to it.
    $.each(data, function(index, value) {
      if (oldData[index] != undefined) {
        data[index]["previousEndAngle"] = oldData[index].endAngle;
      } else {
        data[index]["previousEndAngle"] = 0;
      }
    });
    
    var arcpaths = segments.selectAll("path")
      .data(data);
    
    arcpaths.enter().append("path")
      .style("fill", function(d, i) {
        return color(i);
      })
      .transition()
      .ease(d3.easeElastic)
      .duration(750)
      .attrTween("d", function(d) {
        return arcTween(d, thickness, radius);
      });
    
    arcpaths.transition()
      .ease(d3.easeElastic)
      .style("fill", function(d, i) {
        return color(i);
      })
      .duration(750)
      .attrTween("d", function(d) {
        return arcTween(d, thickness, radius);
      });
    
    arcpaths.exit().transition()
      .ease(d3.easeBounce)
      .duration(750)
      .attrTween("d", function(d) {
        return arcTween(d, thickness, radius);
      })
      .remove();
    
    function arcTween(b, thickness, ir) {
      var prev = JSON.parse(JSON.stringify(b));
      prev.endAngle = b.previousEndAngle;
      var i = d3.interpolate(prev, b);
    
      return function(t) {
        return getArc(thickness, ir)(i(t));
      };
    }
    
    function getRadiusRing(ir, i) {
      return ir - (i * (thickness + spacing));
    }
    
    function getArc(thickness, ir) {
      var arc = d3.arc()
        .innerRadius(function(d) {
          return getRadiusRing(ir, d.index);
        })
        .outerRadius(function(d) {
          return getRadiusRing(ir + thickness, d.index);
        })
        .startAngle(function(d, i) {
          return d.startAngle;
        })
        .endAngle(function(d, i) {
          return d.endAngle;
        });
      return arc;
    }
    
    function setData(data, r) {
      var diameter = (2 * Math.PI) * r;
    
      var segmentValueSum = 0;
      $.each(data, function(ri, va) {
        segmentValueSum += va.value;
      });
    
      $.each(data, function(ri, va) {
        var segmentValue = va.value;
    
        var fraction = segmentValue / segmentValueSum;
    
        var arcBatchLength = fraction * (2 * Math.PI);
        var arcPartition = arcBatchLength;
        var startAngle = Math.PI;
        var endAngle = startAngle + arcPartition;
    
        data[ri]["startAngle"] = startAngle;
        data[ri]["endAngle"] = endAngle;
        data[ri]["index"] = ri;
      });
    
      return data;
    }
    
    
    //legend
    var legendsvgw = 150;
    var legendsvgh = 100;
    
    var ringRadius = 5;
    var vertical = 20;
    
    var legendsvg = d3.select($this[0])
      .append("svg")
      .attr("width", legendsvgw)
      .attr("height", legendsvgh)
      .append("g")
      .attr('class', 'legendsvg')
      .attr("transform", "translate(" + 10 + "," + 10 + ")");
    
    var legend = legendsvg.append("g")
      .attr("class", "legend");
    
    
    var labels = legend.selectAll("text.labels")
      .data(data);
    
    labels.enter().append("text")
      .attr("class", "labels")
      .attr("dx", 15)
      .attr("dy", function(d, i) {
        return (vertical * i) + ringRadius * 2;
      })
      .attr("text-anchor", function(d) {
        return "start";
      })
      .text(function(d) {
        return d.label;
      });
    
    labels.exit().remove();
    
    
    var ring = legend.selectAll("circle")
      .data(data);
    
    ring.enter().append("circle")
      .attr("cy", function(d, i) {
        return (vertical * i) + ringRadius;
      })
      .attr("r", ringRadius)
      .attr("width", ringRadius * 2)
      .attr("height", ringRadius * 2)
      .style("fill", function(d, i) {
        return color(i);
      });
    
    ring.exit().remove();
    body {
      background: #eeeeee;
    }
    
    .arc path {
      stroke: #fff;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <h1>CrescentChart I</h1>
    <div class="crescentchart" data-width="300" data-height="300" data-r="90" data-thickness="6" data-spacing="10" />
    0 回复  |  直到 4 年前
        1
  •  0
  •   Ruben Helsloot Rez    4 年前

    为了使它与您当前的实现保持一致,我会考虑两种不同的情况。其中一个 d.fraction <= 0.25 ,因此,绘制杆只需要圆弧部分。在这种情况下,只需使用圆弧。另一个,什么时候 d.fraction > 0.25 ,你可以自己画,因为你可以假设弧线正好从180度到270度,在闭合之前,你只需要画一条弧线就可以了。我曾经 d3.path() 对于本例,我建议您在阅读代码时将其放在旁边。

    var $this = $('.crescentchart');
    
    var data = [{
        label: 'Fudge Brownie',
        value: 45,
      },
      {
        label: 'Cherry Vanilla',
        value: 60,
      },
      {
        label: 'Pistachio',
        value: 5,
      },
      {
        label: 'Caramel',
        value: 10,
      }
    ];
    
    var oldData = "";
    
    var width = $this.data('width'),
      height = $this.data('height'),
      radius = $this.data('r'),
      thickness = $this.data("thickness"),
      spacing = $this.data("spacing");
    
    var color = d3.scaleOrdinal()
      .range(["#bc658d", "#82c4c3", "#f9d89c", "#f5a7a7"]);
    
    var svg = d3.select($this[0])
      .append("svg")
      .attr("width", width)
      .attr("height", height)
      .append("g")
      .attr('class', 'crescentchart')
      .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
    
    var segments = svg.append('g').attr('class', 'segments');
    
    var data = setData(data, radius);
    
    //append previous value to it.
    $.each(data, function(index, value) {
      if (oldData[index] != undefined) {
        data[index]["previousFraction"] = oldData[index].fraction;
      } else {
        data[index]["previousFraction"] = 0;
      }
    });
    
    var arcpaths = segments.selectAll("path")
      .data(data);
    
    arcpaths.enter().append("path")
      .style("fill", function(d, i) {
        return color(i);
      })
      .transition()
      .ease(d3.easeElastic)
      .duration(3000)
      .attrTween("d", function(d) {
        return arcTween(d, thickness, radius);
      });
    
    arcpaths.transition()
      .ease(d3.easeElastic)
      .style("fill", function(d, i) {
        return color(i);
      })
      .duration(3000)
      .attrTween("d", function(d) {
        return arcTween(d, thickness, radius);
      });
    
    arcpaths.exit().transition()
      .ease(d3.easeBounce)
      .duration(3000)
      .attrTween("d", function(d) {
        return arcTween(d, thickness, radius);
      })
      .remove();
    
    function arcTween(b, thickness, ir) {
      var prev = JSON.parse(JSON.stringify(b));
      prev.endAngle = b.previousEndAngle;
      prev.fraction = b.previousFraction;
      var i = d3.interpolate(prev, b);
      const arc = getArc(thickness, ir);
    
      return function(t) {
        return arc(i(t));
      };
    }
    
    function getRadiusRing(ir, i) {
      return ir - (i * (thickness + spacing));
    }
    
    // The portion of the fraction that is drawn as an arc,
    // instead of as a straight part
    const arcPortion = 3 / 8;
    
    function getArc(thickness, ir) {
      var arc = d3.arc()
        .innerRadius(function(d) {
          return getRadiusRing(ir, d.index);
        })
        .outerRadius(function(d) {
          return getRadiusRing(ir + thickness, d.index);
        })
        .startAngle(- arcPortion * 2 * Math.PI)
        .endAngle(function(d, i) {
          return -(d.fraction + arcPortion) * 2 * Math.PI;
        });
      
      // This function is only called when endAngle is greater than
      // 3 * Math.PI, otherwise we simply draw an arc, because it makes
      // no difference.
      function jShape(d, i) {
        const context = d3.path();
        const innerRadius = getRadiusRing(ir, d.index);
        const outerRadius = getRadiusRing(ir + thickness, d.index);
        const startAngle = -arcPortion * 2 * Math.PI - Math.PI / 2;
        const endAngle = 0;
    
        // Start at the correct position on the inside
        context.moveTo(
          innerRadius * Math.cos(startAngle),
          innerRadius * Math.sin(startAngle)
        );
        
        context.arc(0, 0, innerRadius, startAngle, endAngle, true);
        
        // Now draw the straight part
        const fullLength = innerRadius * 2 * Math.PI;
        
        // The first 0.25 corresponds to the curved part drawn earlier.
        const straightLength = (d.fraction - arcPortion) * fullLength
        context.lineTo(
          innerRadius * Math.cos(endAngle),
          innerRadius * Math.sin(endAngle) - straightLength
        );
    
        // Move to the outside
        context.lineTo(
          outerRadius * Math.cos(endAngle),
          innerRadius * Math.sin(endAngle) - straightLength
        );
        
        context.lineTo(
          outerRadius * Math.cos(endAngle),
          outerRadius * Math.sin(endAngle)
        );
    
        // And curve back
        context.arc(0, 0, outerRadius, endAngle, startAngle, false);
        context.closePath();
    
        return context + "";
      }
      
      return function(d, i) {
        return d.fraction <= arcPortion ? arc(d, i) : jShape(d, i);
      }
    }
    
    function setData(data, r) {
      var segmentValueSum = 0;
      $.each(data, function(ri, va) {
        segmentValueSum += va.value;
      });
    
      $.each(data, function(ri, va) {
        var segmentValue = va.value;
        var fraction = segmentValue / segmentValueSum;
    
        data[ri]["index"] = ri;
        data[ri]["fraction"] = fraction;
      });
    
      return data;
    }
    body {
      background: #eeeeee;
    }
    
    .arc path {
      stroke: #fff;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://d3js.org/d3.v4.js"></script>
    <h1>CrescentChart I</h1>
    <div class="crescentchart" data-width="300" data-height="300" data-r="90" data-thickness="6" data-spacing="10" />