IdontEvenEven
IdontEvenEven

Reputation: 155

How do I draw line from the outer most arc of the circle

I am trying to draw the label line the outer most point of the circle like this picture.

enter image description here

var svg = d3.select("body")
  .append("svg")
  .append("g")

svg.append("g")
  .attr("class", "slices");
svg.append("g")
  .attr("class", "labels");
svg.append("g")
  .attr("class", "lines");

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

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

var arc = d3.svg.arc()
  .outerRadius(radius * 0.8)
  .innerRadius(radius * 0.4);

var outerArc = d3.svg.arc()
  .innerRadius(radius * 0.9)
  .outerRadius(radius * 0.9);

svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

var key = function(d) {
  return d.data.label;
};

var color = d3.scale.ordinal()
  .domain(["53% KILLED 2791", "dolor sit", "amet", "consectetur", "adipisicing", "elit", "sed", "do", "eiusmod", "tempor", "incididunt"])
  .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);

function randomData() {
  var labels = color.domain();
  return labels.map(function(label) {
    return {
      label: label,
      value: Math.random()
    }
  });
}

change(randomData());

d3.select(".randomize")
  .on("click", function() {
    change(randomData());
  });


function change(data) {

  /* ------- PIE SLICES -------*/
  var slice = svg.select(".slices").selectAll("path.slice")
    .data(pie(data), key);

  slice.enter()
    .insert("path")
    .style("fill", function(d) {
      return color(d.data.label);
    })
    .attr("class", "slice");

  slice
    .transition().duration(1000)
    .attrTween("d", function(d) {
      this._current = this._current || d;
      var interpolate = d3.interpolate(this._current, d);
      this._current = interpolate(0);
      return function(t) {
        return arc(interpolate(t));
      };
    })

  slice.exit()
    .remove();

  /* ------- TEXT LABELS -------*/

  var text = svg.select(".labels").selectAll("text")
    .data(pie(data), key);

  text.enter()
    .append("text")
    .attr("dy", ".35em")
    .text(function(d) {
      return d.data.label;
    });

  function midAngle(d) {
    return d.startAngle + (d.endAngle - d.startAngle) / 2;
  }

  text.transition().duration(1000)
    .attrTween("transform", function(d) {
      this._current = this._current || d;
      var interpolate = d3.interpolate(this._current, d);
      this._current = interpolate(0);
      return function(t) {
        var d2 = interpolate(t);
        var pos = outerArc.centroid(d2);
        pos[0] = radius * (midAngle(d2) < Math.PI ? 1 : -1);
        return "translate(" + pos + ")";
      };
    })
    .styleTween("text-anchor", function(d) {
      this._current = this._current || d;
      var interpolate = d3.interpolate(this._current, d);
      this._current = interpolate(0);
      return function(t) {
        var d2 = interpolate(t);
        return midAngle(d2) < Math.PI ? "start" : "end";
      };
    });

  text.exit()
    .remove();

  /* ------- SLICE TO TEXT POLYLINES -------*/

  var polyline = svg.select(".lines").selectAll("polyline")
    .data(pie(data), key);

  polyline.enter()
    .append("polyline");

  polyline.transition().duration(1000)
    .attrTween("points", function(d){
      this._current = this._current || d;
        console.log('_current = ' + JSON.stringify(this._current));
        console.log('d = ' + JSON.stringify(d));
      var interpolate = d3.interpolate(this._current, d);
        console.log('interpolate = ' + JSON.stringify(interpolate(0)));
      this._current = interpolate(0);
      return function(t) {
        var d2 = interpolate(t);
          console.log('t = ' + JSON.stringify(t));
        console.log('d2 = ' + JSON.stringify(d2));
        var pos = outerArc.centroid(d2);
        pos[0] = radius * 0.95 * (midAngle(d2) < Math.PI ? 1 : -1);
        return [arc.centroid(d2), outerArc.centroid(d2), pos];
      };        
    }); 

  polyline.exit()
    .remove();
};
body {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  width: 960px;
  height: 500px;
  position: relative;
}

svg {
  width: 100%;
  height: 100%;
}

path.slice {
  stroke-width: 2px;
}

polyline {
  opacity: .3;
  stroke: black;
  stroke-width: 2px;
  fill: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<button class="randomize">randomize</button>

I am new to D3 and not too sure how to calculate the points to achieve this. I tried looking up formulas what calculating circles but wasn't sure which formula is best for this situation.

Any help is greatly appreciated.

Upvotes: 1

Views: 607

Answers (1)

Terry
Terry

Reputation: 66103

Based on your code, you can see that the lines connecting the arc segments to the text is drawn by this code (near the end of your snippet):

return [arc.centroid(d2), outerArc.centroid(d2), pos];

It basically draws:

  1. from the center of the segment, arc.centroid(d2) (which is drawn between the 0.4 to 0.8 of the radius)
  2. makes a stop at the virtual outer arc, outerArc.centroid(d2) (which is drawn at 0.9) of the radius: this is where the line bends
  3. and finally ends at pos where the text label is anchored at

So, if you want it to start outside the main pie chart, simply supply a new arc to the first argument. Let's give that arc a radius of 0.82, for example:

var polylineStartArc = d3.svg.arc()
  .innerRadius(radius * 0.82)
  .outerRadius(radius * 0.82);

And then you ammend your polyline drawing logic with this:

return [polylineStartArc.centroid(d2), outerArc.centroid(d2), pos];

Of course, you can adjust this value between the range of 0.8 and 0.9:

  • you want to keep it >0.8 so that it does not intersect with the pie chart
  • you want to keep it <0.9 so that it does not go further out than the "bend" in the line

See proof-of-concept below:

var svg = d3.select("body")
  .append("svg")
  .append("g")

svg.append("g")
  .attr("class", "slices");
svg.append("g")
  .attr("class", "labels");
svg.append("g")
  .attr("class", "lines");

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

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

var arc = d3.svg.arc()
  .outerRadius(radius * 0.8)
  .innerRadius(radius * 0.4);

var outerArc = d3.svg.arc()
  .innerRadius(radius * 0.9)
  .outerRadius(radius * 0.9);

var polylineStartArc = d3.svg.arc()
  .innerRadius(radius * 0.82)
  .outerRadius(radius * 0.82);

svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

var key = function(d) {
  return d.data.label;
};

var color = d3.scale.ordinal()
  .domain(["53% KILLED 2791", "dolor sit", "amet", "consectetur", "adipisicing", "elit", "sed", "do", "eiusmod", "tempor", "incididunt"])
  .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);

function randomData() {
  var labels = color.domain();
  return labels.map(function(label) {
    return {
      label: label,
      value: Math.random()
    }
  });
}

change(randomData());

d3.select(".randomize")
  .on("click", function() {
    change(randomData());
  });


function change(data) {

  /* ------- PIE SLICES -------*/
  var slice = svg.select(".slices").selectAll("path.slice")
    .data(pie(data), key);

  slice.enter()
    .insert("path")
    .style("fill", function(d) {
      return color(d.data.label);
    })
    .attr("class", "slice");

  slice
    .transition().duration(1000)
    .attrTween("d", function(d) {
      this._current = this._current || d;
      var interpolate = d3.interpolate(this._current, d);
      this._current = interpolate(0);
      return function(t) {
        return arc(interpolate(t));
      };
    })

  slice.exit()
    .remove();

  /* ------- TEXT LABELS -------*/

  var text = svg.select(".labels").selectAll("text")
    .data(pie(data), key);

  text.enter()
    .append("text")
    .attr("dy", ".35em")
    .text(function(d) {
      return d.data.label;
    });

  function midAngle(d) {
    return d.startAngle + (d.endAngle - d.startAngle) / 2;
  }

  text.transition().duration(1000)
    .attrTween("transform", function(d) {
      this._current = this._current || d;
      var interpolate = d3.interpolate(this._current, d);
      this._current = interpolate(0);
      return function(t) {
        var d2 = interpolate(t);
        var pos = outerArc.centroid(d2);
        pos[0] = radius * (midAngle(d2) < Math.PI ? 1 : -1);
        return "translate(" + pos + ")";
      };
    })
    .styleTween("text-anchor", function(d) {
      this._current = this._current || d;
      var interpolate = d3.interpolate(this._current, d);
      this._current = interpolate(0);
      return function(t) {
        var d2 = interpolate(t);
        return midAngle(d2) < Math.PI ? "start" : "end";
      };
    });

  text.exit()
    .remove();

  /* ------- SLICE TO TEXT POLYLINES -------*/

  var polyline = svg.select(".lines").selectAll("polyline")
    .data(pie(data), key);

  polyline.enter()
    .append("polyline");

  polyline.transition().duration(1000)
    .attrTween("points", function(d){
      this._current = this._current || d;
      var interpolate = d3.interpolate(this._current, d);
      this._current = interpolate(0);
      return function(t) {
        var d2 = interpolate(t);
        var pos = outerArc.centroid(d2);
        pos[0] = radius * 0.95 * (midAngle(d2) < Math.PI ? 1 : -1);
        return [polylineStartArc.centroid(d2), outerArc.centroid(d2), pos];
      };        
    }); 

  polyline.exit()
    .remove();
};
body {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  width: 960px;
  height: 500px;
  position: relative;
}

svg {
  width: 100%;
  height: 100%;
}

path.slice {
  stroke-width: 2px;
}

polyline {
  opacity: .3;
  stroke: black;
  stroke-width: 2px;
  fill: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<button class="randomize">randomize</button>

Upvotes: 2

Related Questions