Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use linkRadial to draw a link between two points?

I need to manually draw some links between points on a circle and points clustered in the centre of said circle. I have the x,y pairs for the source and target points, but I don't want a simple straight line between them; I want a curved line (similar to the links that are found in tree diagrams). I could use linkHorizontal or linkVertical but their tangents are constant; I want to use linkRadial and have the tangent be the radial line at that particular arc point (the degree of which I also have).

I don't understand the linkRadial API though; it wants an angle and a radius, not an x or y point. How do I convert my two x,y pairs (and the radial line angle) into the angle and radius this is expecting?

like image 902
IronWaffleMan Avatar asked Jul 06 '17 21:07

IronWaffleMan


1 Answers

Since you have a data array with the x and y positions ("I have the x,y pairs for the source and target points"), you'll have to convert them to angle and radius. Let's see how to do it.

Firstly, let's see an example with fixed coordinates. For instance, suppose you have this data array, with the x and y positions:

var data = [{
    source: {y: 150,x: 75
    },
    target: {y: 300,x: 0
    }
}, {
    source: {y: 150,x: 75
    },
    target: {y: 0,x: 0
    }
}, {
    source: {y: 150,x: 75
    },
    target: {y: 150,x: 150
    }
}, ];

Using this link generator...

var link = d3.linkHorizontal()
    .x(function(d) {
        return d.y;
    })
    .y(function(d) {
        return d.x;
    });

... you'll have a chart like this:

var data = [{
  source: {
    y: 150,
    x: 75
  },
  target: {
    y: 300,
    x: 0
  }
}, {
  source: {
    y: 150,
    x: 75
  },
  target: {
    y: 0,
    x: 0
  }
}, {
  source: {
    y: 150,
    x: 75
  },
  target: {
    y: 150,
    x: 150
  }
}, ];

var svg = d3.select("svg");

var link = d3.linkHorizontal()
  .x(function(d) {
    return d.y;
  })
  .y(function(d) {
    return d.x;
  });

svg.selectAll(null)
  .data(data)
  .enter()
  .append("path")
  .attr("fill", "none")
  .attr("stroke", "blue")
  .attr("d", link);
<script src="https://d3js.org/d3.v4.js"></script>
<svg></svg>

How can we convert this to a data set that can be used with d3.linkRadial()?

One option is iterating with each object, using basic trigonometry to populate the angle and radius properties:

var radialData = data.map(function(d) {
    return {
        source: {
            x: 0,
            y: 0
        },
        target: {
            x: Math.atan2(d.target.y - d.source.y, d.target.x - d.source.x) - Math.PI,
            y: Math.sqrt((d.target.x - d.source.x) * (d.target.x - d.source.x) + (d.target.y - d.source.y) * (d.target.y - d.source.y))
        }
    };
});

Then, using this link generator:

var linkRadial = d3.linkRadial()
    .angle(function(d) {
        console.log(d)
        return d.x;
    })
    .radius(function(d) {
        return d.y;
    });

We'll have this:

var data = [{
  source: {
    y: 150,
    x: 75
  },
  target: {
    y: 300,
    x: 0
  }
}, {
  source: {
    y: 150,
    x: 75
  },
  target: {
    y: 0,
    x: 0
  }
}, {
  source: {
    y: 150,
    x: 75
  },
  target: {
    y: 150,
    x: 150
  }
}, ];

var svg = d3.select("svg");

var radialData = data.map(function(d) {
  return {
    source: {
      x: 0,
      y: 0
    },
    target: {
      x: Math.atan2(d.target.y - d.source.y, d.target.x - d.source.x) - Math.PI,
      y: Math.sqrt((d.target.x - d.source.x) * (d.target.x - d.source.x) + (d.target.y - d.source.y) * (d.target.y - d.source.y))
    }
  };
});

var g = svg.append("g")
  .attr("transform", "translate(150,75)")

var linkRadial = d3.linkRadial()
  .angle(function(d) {
    return d.x;
  })
  .radius(function(d) {
    return d.y;
  });

g.selectAll(null)
  .data(radialData)
  .enter()
  .append("path")
  .attr("fill", "none")
  .attr("stroke", "red")
  .attr("d", linkRadial);
<script src="https://d3js.org/d3.v4.js"></script>
<svg></svg>

Now both generators together, for comparison:

var data = [{
  source: {
    y: 150,
    x: 75
  },
  target: {
    y: 300,
    x: 0
  }
}, {
  source: {
    y: 150,
    x: 75
  },
  target: {
    y: 0,
    x: 0
  }
}, {
  source: {
    y: 150,
    x: 75
  },
  target: {
    y: 150,
    x: 150
  }
}, ];

var svg = d3.select("svg");

var link = d3.linkHorizontal()
  .x(function(d) {
    return d.y;
  })
  .y(function(d) {
    return d.x;
  });

svg.selectAll(null)
  .data(data)
  .enter()
  .append("path")
  .attr("fill", "none")
  .attr("stroke", "blue")
  .attr("d", link);

var radialData = data.map(function(d) {
  return {
    source: {
      x: 0,
      y: 0
    },
    target: {
      x: Math.atan2(d.target.y - d.source.y, d.target.x - d.source.x) - Math.PI,
      y: Math.sqrt((d.target.x - d.source.x) * (d.target.x - d.source.x) + (d.target.y - d.source.y) * (d.target.y - d.source.y))
    }
  };
});

var g = svg.append("g")
  .attr("transform", "translate(150,75)")

var linkRadial = d3.linkRadial()
  .angle(function(d) {
    return d.x;
  })
  .radius(function(d) {
    return d.y;
  });

g.selectAll(null)
  .data(radialData)
  .enter()
  .append("path")
  .attr("fill", "none")
  .attr("stroke", "red")
  .attr("d", linkRadial);
<script src="https://d3js.org/d3.v4.js"></script>
<svg></svg>
like image 198
Gerardo Furtado Avatar answered Nov 15 '22 06:11

Gerardo Furtado