ablackman93
ablackman93

Reputation: 45

D3 small circle located on edge of radius of larger circle

I am working on a d3 visual with two circles where the smaller circle is placed on the edge of the radius of the larger circle. A codepen of this is located here https://codepen.io/ajblack/pen/KqKpde. I have a 200 pixel square SVG that I am placing these circles in, and then I have two values, uAssets and uAssetsCom that are dictating the sizes of the circles. I create a ratio to ensure that the larger circle fits in the 200 pixel square using this code:

var ratioToFit = (200/uAssets)/2;

Then I use cosine to calculate the distance between the outer radius of the large circle and the outer bound of the SVG container at a 45 degree angle:

var key = 100*((1-Math.cos(45*Math.PI/180))/Math.cos(45*Math.PI/180))

To place the smaller circle I calculate cx and cy attributes as the radius of the inner circle plus this 'key'.

d3.select("#uAssetComCircle")
  .attr("cx", function(d){return (d.radius+key);})
  .attr("cy", function(d){return (d.radius+key);})

This places the inner circle close to the radius of the larger circle but it's about 2 pixels off. I can offset this with a hardcoded 2 pixel addition to cx and cy but I feel like I shouldn't have to.

Is my math off?

Upvotes: 0

Views: 943

Answers (2)

ablackman93
ablackman93

Reputation: 45

My solution is posted here https://codepen.io/ajblack/pen/qjOqmQ . This implementation uses the suggestions of scale and transform. The problem that I was having was a misuse of the Pythagorean Theorem. The radius of my larger circle is 100px and I can use this as the hypotenuse of a right triangle. I know that I want my smaller circle to be offset at a 45 degree angle so I know that the two other sides of the triangle will be the same length. Using the formula a²+b²=c² where a and b are the same value and c is equal to 100px minus the radius of my small circle I can easily solve for a and b.

var uAssets = 17;
var uAssetsCom = 3;
var scale = d3.scaleLinear()
.domain([0,uAssets])
.range([0,100]);
 d3.select("#uAssetComCircle")
    .attr("cx", function(d) {
    return Math.sqrt(Math.pow(100-scale(uAssetsCom),2)/2);
  })
    .attr("cy", function(d) {
    return Math.sqrt(Math.pow(100-scale(uAssetsCom),2)/2);
  });

The finished product looks like thissmall circle set inside large circle radius

Upvotes: 1

Mark
Mark

Reputation: 108567

Not sure I completely follow your calculations, but here's a quick refactor that introduces a few things:

  1. A translation to base all the drawing from the center point of the SVG
  2. A d3 scale that handles calculating from user coordinates to pixel coordinates.

<!DOCTYPE html>
<html>

<head>
  <script data-require="[email protected]" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
  <link rel="stylesheet" href="style.css" />
  <script src="script.js"></script>
</head>

<body>
  <script>
    var uAssets = 17;
    var uAssetsCom = 3;

    var svgContainer = d3.select("body").append("svg")
      .attr("width", 200)
      .attr("height", 200)
      .attr('id', 'circleContainer')
      .append("g")
      .attr("transform", "translate(" + 100 + "," + 100 + ")")

    var scale = d3.scaleLinear()
      .domain([0,uAssets])
      .range([0,100]);

    var jsonCircles = [{
      "radius": uAssets,
      "color": "green",
      "id": "uAssetCircle"
    }, {
      "radius": uAssetsCom,
      "color": "purple",
      "id": "uAssetComCircle"
    }];

    var circles = svgContainer.selectAll("circle")
      .data(jsonCircles)
      .enter()
      .append("circle");

    var circleAttributes = circles
      .attr("r", function(d) {
        return scale(d.radius);
      })
      .attr("id", function(d) {
        return d.id
      })
      .style("fill", function(d) {
        return d.color;
      });

    d3.select("#uAssetComCircle")
      .attr("cx", function(d) {
        return scale(uAssets) * Math.cos(45 * (Math.PI / 180));
      })
      .attr("cy", function(d) {
        return scale(uAssets) * Math.sin(45 * (Math.PI / 180));
      })
  </script>
</body>

</html>

Upvotes: 1

Related Questions