What is the best way to connect a line from the edge of the circle's outermost arc?

I am attempting to create a label line that extends from the outermost point of the circle, similar to what is shown in this image.

https://i.sstatic.net/OqC0p.png

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>

This task is new to me and I am not confident in how to calculate the points required for achieving this effect. I attempted to research formulas for calculating circles but I am unsure which formula would be most suitable for this specific scenario.

Any assistance would be highly appreciated.

Answer №1

After reviewing your code, it appears that the lines connecting the arc segments to the text are generated by the following code snippet (located towards the end of your code):

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

This code essentially does the following:

  1. Starts from the center of the segment at arc.centroid(d2) (positioned between 0.4 to 0.8 of the radius)
  2. Moves to the virtual outer arc at outerArc.centroid(d2) (positioned at 0.9 of the radius) where the line curves
  3. Ends at pos, which is where the text label is anchored

If you wish for the line to start outside the main pie chart, provide a new arc as the first argument. For example, assigning a radius of 0.82 to this new arc:

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

You can then update your polyline drawing logic with the following:

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

You have the flexibility to adjust this value within the range of 0.8 and 0.9:

  • Maintain it >0.8 to prevent intersection with the pie chart
  • Ensure it's <0.9 so it doesn't extend past the "bend" in the line

Refer to the proof-of-concept implementation below:

// Your JavaScript code goes here
// Your CSS code goes here
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<button class="randomize">Randomize</button>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Converting objects to arrays in reactJS

Here is the object structure I currently have: { DepositAmt_0: 133 DepositAmt_1: 455 DepositAmt_2: 123 DepositNotes_0: "some notes " DepositNotes_1: "some comment" DepositNotes_2: "some comme ...

Steps to enable navigation to external pages from a ReactJS app

I am working on a simple ReactJS application: [Demo] [Source] I am trying to implement navigation within the app from external sources without refreshing the web page. This functionality should be similar to using this.props.history.push(...). /public/i ...

Unable to retrieve data from SpringBoot controller using $http.get request

I have developed an application that will execute queries on my store's database based on user input on the webpage. The backend method is functioning correctly and returns the response, but I am having trouble displaying the data in a dynamic table o ...

Tips for showcasing heading and paragraph elements side by side in css?

I have a block of HTML code that includes a heading and two paragraphs: <div class="inter"> <h4>Note</h4> <p>To add/remove a dependent or to modify your spouse's insurer information, go to the My Life Events section and foll ...

Disabling a button following a POST request

Is there a way to prevent multiple clicks on a button after a post request is made? I want the button to be disabled as soon as it is clicked, before the post request is executed. Below is an example of my code where the button is supposed to be disabled w ...

Tips for aligning buttons vertically within the form-row class in Bootstrap 4

I'm facing an issue where I cannot get a set of buttons in line with labels when using different column widths in Bootstrap. The behavior of a button assigned to a specific column width, like col-3, is not the same as a button assigned with automatic ...

Managing the vertical space within a nested accordion section

I've built a custom accordion component, but I'm encountering scrolling issues when trying to use nested levels within the accordion. I'd like to prevent scrolling inside the accordion section and instead have the page scroll below it. Any ...

What are the steps for establishing a connection to Heroku using node-mongodb-native?

I'm feeling lost when it comes to connecting to MongoLab on Heroku. I came across an example that was supposed to help, but it just left me more confused. You can check it out here. After looking at both the web.js and deep.js files, I noticed they b ...

Rendering High-quality Images in Internet Explorer Browser

Currently, I am working on optimizing my website for high resolution monitors, particularly the new iPad. The site is already formatted to my liking, with images having increased resolutions while still fitting into specific DIVs. For instance, an image wi ...

Localization of date picker in material-table(Material UI)

Does anyone have experience with localizing the date picker in material-table (Material UI)? The date picker is specifically used for filtering in this scenario. import React from 'react'; import MaterialTable from 'material-table'; fu ...

How can I modify the color scheme of radio buttons to include a variety of different hues?

Is there a way to customize the colors of my radio buttons with three different options? I want numbers 1, 2, and 3 to be red, number 4 to be orange, and number 5 to be green... Here is the code I am using: /* Option 1 */ input[type='radio'] { ...

The MVC Controller is unable to retrieve decimal values from an Ajax POST request

I am facing an issue with the POST function in my code. While string and integer values are reaching the Controller without any problem, double values are not being received on the server side. Interestingly, when I test on my local machine, everything wor ...

How do I insert a new column for the Name attribute in ASP.NET Core Identity without using the Username field?

I'm currently utilizing ASP.NET Core Identity within my project. I have a form that includes fields for Name and Username. The issue arises when attempting to save values from the Name field, as it triggers an error stating that they are not in the co ...

Error: The options object provided for CSS Loader is not valid and does not match the API schema. Please make sure to provide the correct options when

Summary My Nuxt.js project was created using the command yarn create nuxt-app in SPA mode. However, I encountered an error after installing Storybook where running yarn dev resulted in failure to start the demo page. ERROR Failed to compile with 1 errors ...

The addClass and removeClass functions seem to be malfunctioning

Hey everyone, this is my first time reaching out for help here. I looked through previous questions but couldn't find anything similar to my issue. I'm currently working on a corporate website using bootstrap3 in Brackets. I've been testing ...

Guide to importing an AngularJS controller into an Express file (routes.js)

Currently, I am in the process of developing a restful service and my goal is to organize my callbacks within controllers in order to avoid cluttering my routes.js file. Previously, I had been using controller = require(path.to.controller); This enabled ...

box tick does not alter appearance

I've been struggling with this seemingly simple issue for an hour now. I have a radio button set up with two buttons: <input class="form-control icheck" id="cash_prize" name="cash_prize" type="radio" value="1" style="position: absolute; opacity: 0 ...

Clicking on the overlaying divs in the slideshow does not trigger any changes when clicked

My question is about the behavior of the href attribute in a fade-style animation. Even when different containers are visible, the href value remains constant. Below is the HTML code: <div id="teaserslider"> <ul> <li> ...

Is there a way to verify if an ID includes more than one word?

I am trying to target a specific div with a unique id in jQuery: <div id="picture_contents_12356_title"></div> The '12356' is autogenerated and must be included in the id. I need to create a jQuery selector that combines "picture_co ...

"Exploring the concept of odd and even numbers within the ng-repeat

Can anyone help me with detecting odd or even numbers in an ng-repeat? I have created a fiddle that displays some dots randomly showing and hiding. Now, I want to change the background color so that odd numbers are green and even numbers are red. function ...