Reputation: 45
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
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 this
Upvotes: 1
Reputation: 108567
Not sure I completely follow your calculations, but here's a quick refactor that introduces a few things:
<!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