Dougnut chart: calculate outer circle's coordinates to get parallel gaps between slices

Viewed 72

I have a custom Dougnut chart drawer, which can draw doughnuts like this:

enter image description here

I'm pretty satisfied with this, however I'd like to finetune it and I just have no idea of the right solution.

I'd like to have parallel gaps (paddings) between slices.

I raised those paddings to have better view of my goal.

This is how I'm drawing this currently:

double cx, cy; //center points of circle
double r1, r2; //radius of outer and inner circle
double pad = M_PI / 360 * 12; //12 degree pad
double alpha = -1 * M_PI / 2; //starting from up (noon)
double da = 0.0; // delta of current slice

for (ALL_SLICES) {
  calculate_da(&da); //calculate slice's delta

  // drawing
  move_pen(cx + r2 * cos(alpha + pad/2), cy + r2 * sin(alpha + pad/2)); //STEP1
  draw_arc(cx, cy, r1, alpha + pad/2, alpha + da - pad/2); //STEP2
  draw_negative_arc(cx, cy, r2, alpha + da - pad/2, alpha + pad/2); //STEP3
  fill();
  
  //update next alpha
  alpha += da;
}

Drawing steps:

enter image description here

So this is fairly simple, however to have parallel gaps between slices, I'd have to calculate another angle for the outer end and start points:

enter image description here

I've drawn quite a lot of triangles already but I was not able to solve this easily.

Again: the goal is to have parallel paddings between the slices, i.e. bringing these 2 red dots closer together on the circumference, to get exact parallel lines between two slices.

Update:

This is how it looks with scaled padding from @chux answer:

enter image description here

Unfortunately it's not perfect, these lines are not parallel:

enter image description here

3 Answers

"Pad should be 12degree" --> If you want parallel lines, the inner padding angle and outer padding angles are different - not both 12°.

Use a scaled padding.

Given the outer padding angle is double pad = ...,
Draw first arc as before.

draw_arc(cx, cy, r1, alpha + pad/2, alpha + da - pad/2); //STEP2

The inner padding angle is proportionally larger.

double inner_pad = pad*r1/r2;
draw_negative_arc(cx, cy, r2, alpha + da - inner_pad/2, alpha + inner_pad/2); //STEP3

Tip: Less confusing to use r_inner, r_outer, than r2, r1.


Minor: Padding calculation looks off by 2x.

// double pad = M_PI / 360 * 12; //12 degree pad.
double pad = 2 * M_PI / 360 * 12; //12 degree pad.

We'll solve this for the case of one arbitrary gap. Then, you can apply our solution to any number of gaps.

First, we can calculate the average of the angles at which the two boundaries radiate from the center. This will be the angle at which our two new segments will radiate from the inner circle to the outer circle. If the original segments radiate from the center of the inner circle at angles a and b, let c be the angle going through the middle of the smaller segment of the circle between them. If a = 60 deg and b = 90 deg, choose c = 75 deg as an example.

Now, for the points of intersection with the edge of the inner circle you've already found, place lines whose slopes have angle c (w.r.t. the positive x-axis, as normal). Then, find the correspondibg points of intersection with the outer circle. These new outer points and your old inner points define the parallel segments that you are looking for.

Example: inner circle radius r = 10; outer circle radius 20; angles a = 30 deg and b = 60 deg. Your current inner and outer points are p1 = (5sqrt(3), 5), p2 = (5, 5sqrt(3)), q1 = 2p1, q2 = 2p2 (assume the center of the inner circle is at the origin here). Calculate c = 45 deg. The slope of lines with this angle can be found using tangent; m = 1. Define two lines going through p1 and p2 with slope 1; we get y = x + 5(1 - sqrt(3)) and y = x + 5(sqrt(3) - 1). Now we find the points of intersection of these lines with the outer circle; I will leave this as an exercise but basically just take the equation of the outer circle, x^2 + y^2 = 400, replace y in this with the right-hand side of each equation, and solve for x. You will get two solutions (a line passing through a point inside the outer circle must intersect the circle in two places)... pick the one the is in the smaller segment defined by your original outer points.

This seems pretty tedious to do by hand, and it is, but in reality once you write the code once the computer will have no issue doing this for you all day long.

(Please comment if you need me to try to work out the points of intersection in the example, on my phone now).

For @chux - Reinstate Monica's request, I'm posting here my final solution which relies heavily on his link in comment: Radius and central angle on a circular segment.

1.) Set the initial constants:

double pad = PADDING_VALUE;
double r_inner = INNER_RADIUS;
double r_outer = OUTER_RADIUS;

2.) Calculate the slice's starting point's coordinates (on the inner circle):

double alpha = CURRENT_SLICE_ANGLE;
double inner_angle = alpha + pad/2; // slice will begin with pad/2 offset

double x1 = r_inner * cos(inner_angle);
double y1 = r_inner * sin(inner_angle);

3.) Now calculate the previous slice's ending point's coordinates (also on the inner circle):

double x2 = r_inner * cos(inner_angle - pad); //easy, exactly angle-pad
double y2 = r_inner * sin(inner_angle - pad); //easy, exactly angle-pad

4.) Now I have the coords for the current slice's start and the previous slice's end. I want to keep a constant length between the 2 slices, and this length should be exactly the segment's length between (x1,y1) and (x2,y2). This is a right-angled triangle, and the segment's length I'm looking for is easily expressable with Pitagoras formula:

double hyp = sqrt(pow(x1-x2,2) + pow(y1-y2,2));

5.) This hyp is exactly the chord length (c) here:

enter image description here

6.) From the same Wikipedia page the Theta is expressible with chord length and radius:

double theta = 2 * asin(hyp / 2 / r_outer);

7.) I have to draw the outer arc with the help of theta and pad:

double outer_angle_correction = (pad - theta) / 2;

Applying this calculation results this:

enter image description here

This is a bit odd, as the gaps are a bit too large. But anyway these huge gaps were only used for demonstration, and after I changed them back to my initial intended values this is the result:

enter image description here

Perfectly parallel gaps between all slices without using any approximation - just pure math. Sweet.

Related