Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

selected rounding of corners in arc #195

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,20 @@ If the corner radius is greater than zero, the corners of the arc are rounded us

The corner radius may not be larger than ([outerRadius](#arc_outerRadius) - [innerRadius](#arc_innerRadius)) / 2. In addition, for arcs whose angular span is less than π, the corner radius may be reduced as two adjacent rounded corners intersect. This is occurs more often with the inner corners. See the [arc corners animation](http://bl.ocks.org/mbostock/b7671cb38efdfa5da3af) for illustration.

Individual corners can be rounded by passing in an array of numbers length 1-4 instead of a single number.

- Array length 4: first value applies to top-left corner, second value applies to top-right corner, third value applies to bottom-right corner, and fourth value applies to bottom-left corner

- Array length 3: first value applies to top-left corner, second value applies to top-right and bottom-left corners, and third value applies to bottom-right corner

- Array length 2: first value applies to top-left and bottom-right corners, and the second value applies to top-right and bottom-left corners

- Array length 1 or single number: the value applies to all four corners, which are rounded equally

(This assumes that the arc is being drawn clockwise i.e. end angle > start angle otherwise the order will be flipped, top right corner -> top left corner and bottom right corner -> bottom left corner)

<img alt="Selected Corner Radius Rounding" src="https://raw.githubusercontent.com/d3/d3-shape/master/img/selected-corner-radius-rounding.png" width="1150" height="154">

<a name="arc_startAngle" href="#arc_startAngle">#</a> <i>arc</i>.<b>startAngle</b>([<i>angle</i>]) · [Source](https://github.com/d3/d3-shape/blob/main/src/arc.js)

If *angle* is specified, sets the start angle to the specified function or number and returns this arc generator. If *angle* is not specified, returns the current start angle accessor, which defaults to:
Expand Down
Binary file added img/selected-corner-radius-rounding.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 49 additions & 21 deletions src/arc.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,35 @@ export default function() {
da1 = da,
ap = padAngle.apply(this, arguments) / 2,
rp = (ap > epsilon) && (padRadius ? +padRadius.apply(this, arguments) : sqrt(r0 * r0 + r1 * r1)),
rc = min(abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments)),
rc0 = rc,
rc1 = rc,
mwrc = abs(r1 - r0) / 2,
t0,
t1;
t1,
rc,
rc00,
rc01,
rc10,
rc11,
rci = cornerRadius.apply(this, arguments);

if (Array.isArray(rci)) {
for (var i = 0; i < rci.length; i++) rci[i] = min(mwrc, +rci[i]);
if (rci.length === 1) rc = [rci[0], rci[0], rci[0], rci[0]];
else if (rci.length === 2) rc = [rci[0], rci[1], rci[0], rci[1]];
else if (rci.length === 3) rc = [rci[0], rci[1], rci[2], rci[1]];
else if (rci.length === 4) rc = rci;
rc00 = rc[2];
rc01 = rc[3];
rc10 = rc[0];
rc11 = rc[1];
}
else {
rci = min(mwrc, +rci)
rc = [rci, rci, rci, rci];
rc00 = rc[2];
rc01 = rc[3];
rc10 = rc[0];
rc11 = rc[1];
}

// Apply padding? Note that since r1 ≥ r0, da1 ≥ da0.
if (rp > epsilon) {
Expand All @@ -144,7 +168,7 @@ export default function() {
y10 = r0 * sin(a10);

// Apply rounded corners?
if (rc > epsilon) {
if (max(rc[0], rc[1], rc[2], rc[3]) > epsilon) {
var x11 = r1 * cos(a11),
y11 = r1 * sin(a11),
x00 = r0 * cos(a00),
Expand All @@ -158,30 +182,34 @@ export default function() {
bx = x11 - oc[0],
by = y11 - oc[1],
kc = 1 / sin(acos((ax * bx + ay * by) / (sqrt(ax * ax + ay * ay) * sqrt(bx * bx + by * by))) / 2),
lc = sqrt(oc[0] * oc[0] + oc[1] * oc[1]);
rc0 = min(rc, (r0 - lc) / (kc - 1));
rc1 = min(rc, (r1 - lc) / (kc + 1));
lc = sqrt(oc[0] * oc[0] + oc[1] * oc[1]),
mlrc0 = (r0 - lc) / (kc - 1),
mlrc1 = (r1 - lc) / (kc + 1);
rc00 = min(rc[2], mlrc0);
rc01 = min(rc[3], mlrc0);
rc10 = min(rc[0], mlrc1);
rc11 = min(rc[1], mlrc1);
}
}

// Is the sector collapsed to a line?
if (!(da1 > epsilon)) context.moveTo(x01, y01);

// Does the sector’s outer ring have rounded corners?
else if (rc1 > epsilon) {
t0 = cornerTangents(x00, y00, x01, y01, r1, rc1, cw);
t1 = cornerTangents(x11, y11, x10, y10, r1, rc1, cw);
else if (rc10 > epsilon || rc11 > epsilon) {
t0 = cornerTangents(x00, y00, x01, y01, r1, rc10, cw);
t1 = cornerTangents(x11, y11, x10, y10, r1, rc11, cw);

context.moveTo(t0.cx + t0.x01, t0.cy + t0.y01);

// Have the corners merged?
if (rc1 < rc) context.arc(t0.cx, t0.cy, rc1, atan2(t0.y01, t0.x01), atan2(t1.y01, t1.x01), !cw);
if (rc10 === rc11 && rc10 === mlrc1) context.arc(t0.cx, t0.cy, rc10, atan2(t0.y01, t0.x01), atan2(t1.y01, t1.x01), !cw);

// Otherwise, draw the two corners and the ring.
else {
context.arc(t0.cx, t0.cy, rc1, atan2(t0.y01, t0.x01), atan2(t0.y11, t0.x11), !cw);
context.arc(t0.cx, t0.cy, rc10, atan2(t0.y01, t0.x01), atan2(t0.y11, t0.x11), !cw);
context.arc(0, 0, r1, atan2(t0.cy + t0.y11, t0.cx + t0.x11), atan2(t1.cy + t1.y11, t1.cx + t1.x11), !cw);
context.arc(t1.cx, t1.cy, rc1, atan2(t1.y11, t1.x11), atan2(t1.y01, t1.x01), !cw);
context.arc(t1.cx, t1.cy, rc11, atan2(t1.y11, t1.x11), atan2(t1.y01, t1.x01), !cw);
}
}

Expand All @@ -193,20 +221,20 @@ export default function() {
if (!(r0 > epsilon) || !(da0 > epsilon)) context.lineTo(x10, y10);

// Does the sector’s inner ring (or point) have rounded corners?
else if (rc0 > epsilon) {
t0 = cornerTangents(x10, y10, x11, y11, r0, -rc0, cw);
t1 = cornerTangents(x01, y01, x00, y00, r0, -rc0, cw);
else if (rc00 > epsilon || rc01 > epsilon) {
t0 = cornerTangents(x10, y10, x11, y11, r0, -rc00, cw);
t1 = cornerTangents(x01, y01, x00, y00, r0, -rc01, cw);

context.lineTo(t0.cx + t0.x01, t0.cy + t0.y01);

// Have the corners merged?
if (rc0 < rc) context.arc(t0.cx, t0.cy, rc0, atan2(t0.y01, t0.x01), atan2(t1.y01, t1.x01), !cw);
if (rc00 === rc01 && rc00 === mlrc0) context.arc(t0.cx, t0.cy, rc00, atan2(t0.y01, t0.x01), atan2(t1.y01, t1.x01), !cw);

// Otherwise, draw the two corners and the ring.
else {
context.arc(t0.cx, t0.cy, rc0, atan2(t0.y01, t0.x01), atan2(t0.y11, t0.x11), !cw);
context.arc(t0.cx, t0.cy, rc00, atan2(t0.y01, t0.x01), atan2(t0.y11, t0.x11), !cw);
context.arc(0, 0, r0, atan2(t0.cy + t0.y11, t0.cx + t0.x11), atan2(t1.cy + t1.y11, t1.cx + t1.x11), cw);
context.arc(t1.cx, t1.cy, rc0, atan2(t1.y11, t1.x11), atan2(t1.y01, t1.x01), !cw);
context.arc(t1.cx, t1.cy, rc01, atan2(t1.y11, t1.x11), atan2(t1.y01, t1.x01), !cw);
}
}

Expand Down Expand Up @@ -234,7 +262,7 @@ export default function() {
};

arc.cornerRadius = function(_) {
return arguments.length ? (cornerRadius = typeof _ === "function" ? _ : constant(+_), arc) : cornerRadius;
return arguments.length ? (cornerRadius = typeof _ === "function" ? _ : constant(_), arc) : cornerRadius;
};

arc.padRadius = function(_) {
Expand Down