How to align D3 circles on semicircle

Viewed 153

I have a semi circle that I need to align Radios I created from circles. The number of radios will be dynamic and will need to keep them centered. Here is what I currently have.

UPDATE, just realized that the 2 screenshots can be confusing. The order of the radios are not the same. Ignore that. I just need the alignment to be correct,

CURRENT PROGRESS

This is the desired result.

DESIRED RESULT

I have a working demo here WORKING DEMO

2 Answers

Given your preferred arc angle A and number of points N, find the angular distance between each point:

PA = A / (N - 1).

Next, find the angle described by a vertical line from the circle's center and the point we're calculating.

Initial observations:

  • The largest possible value of this angle is A / 2
  • The smallest possible value is PA / 2 if N is even, 0 if N is odd.
  • The angle changes in increments of PA

Counting points away from the center (X), we can describe the angle using terms we've defined:

A / 2 - PA * (floor(N / 2) - X)

For the examples directly below, X is 1 and 2 respectively.

enter image description here

Assuming:

  • A = 90° (not depicted accurately)
  • N = 4

From which follows:

  • PA = 30°

Plugging in these values, we get:

45 - 30 * (floor(2) - 1) = 15

and

45 - 30 * (floor(2) - 2) = 45

Now that we have the angle a, we can use trigonometry (specifically SOH-CAH-TOA) to find the x and y offsets of the point relative to the center.

Note that because the points to the left of the plumb line are mirror images of their counterparts to the right, you need only to negate the x-offset from the center to get the locations of the left-hand points.

Update: Here's a simpler (but still math-based) implementation.

enter image description here

While backtick's answer is a beautiful math explanation, you can do that with a simple mix of getPointAtLength and getTotalLength:

First you do...

const arcPathLength = arcPath.node().getTotalLength() / 2;

... where arcPath is just that gray arc the radio buttons will follow. Here I'm dividing the length by 2 because the arc goes back to the origin. Then you just do:

svg.selectAll(null)
    .data(RADIO_DATA)
    //etc...
    .attr("cx", (d, i) => arcPath.node().getPointAtLength((arcPathLength/(RADIO_DATA.length - 1)) * i).x)
    .attr("cy", (d, i) => arcPath.node().getPointAtLength((arcPathLength/(RADIO_DATA.length - 1)) * i).y)

Since I cannot fork the code without logging in, here is a screenshot of the result:

enter image description here

Adjust the texts accordingly. Also, pay attention to the fact that circle elements have no d or text attributes.

Related