Search code examples
d3.jsdonut-chart

How to update the innerRadius and the outterRadius of a donut chart in d3?


I'm trying to change the innerRadius and the outterRadius of a donut chart by using a slider.

Here is the JSFiddle link: https://jsfiddle.net/SashimiEthan/woetyLg3/3/

<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="bootstrap.css">
<link href='http://fonts.googleapis.com/css?family=Lato' rel='stylesheet' type='text/css' />
<link rel="stylesheet" type="text/css" href="style.css">
<script src="d3.min.js"></script>
<script scr="color.min.js"></script>
<script scr="d3.slider.js"></script>
</head>
<body>
<div class="content">
  <div class="wrapper" id="wheel"></div>    
</div>
<script>

  var width = 300;
  r = width / 2,
  labelr = r + 20
  outerRadius = 150,
  innerRadius = outerRadius - 30;
  ; // radius for label anchor

  var mySvg = d3.select("#wheel").append("svg")
      .attr("width", 500)
      .attr("height", 500);

  var myGroup = mySvg.append("g")
      .attr("transform", "translate(200,200)" );

  var arc = d3.svg.arc()
                .innerRadius(innerRadius)
                .outerRadius(outerRadius);

  var arc1 = d3.svg.arc()
                .innerRadius(innerRadius-30)
                .outerRadius(outerRadius-30);

  var numberOfSegments = 12;
  var radians;
  var degrees;

  // function render (arc) {
    radians = (Math.PI * 2) / numberOfSegments;
    degrees = 360 / numberOfSegments;

    arc.startAngle(function (d,i) { return radians * i } );
    arc.endAngle(function (d,i) { return radians * (i + 1) });

    var g = myGroup.selectAll("g").data(d3.range(numberOfSegments));

    g.enter().append("g").attr("class", "arc");        

    g.append("path")
      .attr("class", "seg")
      .attr("d", arc)
      .attr("fill", function(d,i) {
        return "hsl(" + (i * degrees) + ",100%,50%)";
      });

    g.append('text').attr("transform", function(d,i) {
      var c = arc.centroid(d,i),
          x = c[0],
          y = c[1],

          // pythagorean theorem for hypotenuse
          h = Math.sqrt(x*x + y*y);
          console.log(c);
          return "translate(" + (x/h * labelr) +  ',' +(y/h * labelr) +         ")"; 
      })
      .attr("dy", ".35em")
      .attr("dx", "-0.9em")
      .attr("text-anchor", function(d) {
          // are we past the center?
          return (d.endAngle + d.startAngle)/2 > Math.PI ?
              "end" : "start";
        })
      .text(function(d,i) {
              return i * degrees + "°";
            });

    g.exit().remove();      
  // }

  // render(arc);

  var margin = {top: 10, right: 50, bottom: 10, left: 50},
  width = 300 - margin.left - margin.right, //controller
  height = 50 - margin.bottom - margin.top; //controller
  startingValue1 = 1;

  var x = d3.scale.linear()
      .domain([0, 1])
      .range([0, width])
      .clamp(true);

  var brush1 = d3.svg.brush()
      .x(x)
      .extent([0, 0]) //brush length
      .on("brush", brushed1);

  var svg = d3.select("body").append("svg") //controller area
      .attr("width", width + margin.left + margin.right)
      .attr("height", height + margin.top + margin.bottom)
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); //move controller

  svg.append("text").text("Saturation")

  var axis  = svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height / 2 + ")")
      .call(d3.svg.axis()
        .scale(x)
        .orient("bottom")
        .tickValues([0, 0.5, 1])
        .tickSize(0)
        .tickPadding(10))
      .select(".domain")
      .select(function() { return this.parentNode.appendChild(this.cloneNode(true)); })
      .attr("class", "halo");

  var slider = svg.append("g")
      .attr("class", "slider")
      .call(brush1);

  slider.selectAll(".extent,.resize")
      .remove();

  slider.select(".background")
      .attr("height", height);

  var handle = slider.append("g")
      .attr("class", "handle")

  handle.append("circle")
      .attr("class","ctl")
      .attr("transform", "translate(0," + height / 2 + ")")
      .attr("r", 8);

  handle.append('text')
  .text(startingValue1)
  .attr("transform", "translate(" + (-5) + " ,0)");

  slider
      .call(brush1.event)
      .call(brush1.extent([1, 1]))
      .call(brush1.event);

  function brushed1() {
    var value = brush1.extent()[0];
    if (d3.event.sourceEvent) {
      handle.select('text');
      value = x.invert(d3.mouse(this)[0]);
      brush1.extent([value, value]);
    }

    handle.attr("transform", "translate(" + x(value) + ",0)");
    var format = d3.format(".1f");
    handle.select('text').text(format(value))

    var newarc = d3.svg.arc()
                .innerRadius(innerRadius-30)
                .outerRadius(outerRadius-30);
    d3.selectAll(".seg").attr("d",newarc);
    d3.selectAll(".seg").style("fill",function(d,i) {
      return d3.hsl(i * degrees, value, 0.5)
      });
  }

</script>

(Sorry the code is messy. I'm new to d3) So I got the saturation adjustment part working, but the radius adjustment part

var newarc = d3.svg.arc()
                .innerRadius(innerRadius-30)
                .outerRadius(outerRadius-30);
    d3.selectAll(".seg").attr("d",newarc)

always gave me the error: Invalid value for attribute d="……"

T^T


Solution

  • When you define newarc after moving the slider, you need to include the startAngle and endAngle attributes, in the same way as you do when you first define arc.

    This is all you need to do:

    var newarc = d3.svg.arc()
          .innerRadius(innerRadius - (30 * value))
          .outerRadius(outerRadius)
          .startAngle(function(d, i) {
              return radians * i
          })
          .endAngle(function(d, i) {
              return radians * (i + 1)
          });
    
      d3.selectAll(".seg").attr("d", newarc);
    

    Note that to make it clearer what's happening when the slider is moved, I've made the innerRadius value dependent on the slider's value.

    See a working fiddle here: http://jsfiddle.net/henbox/vxq9o2a8/1/