15

It seems that the option rounded corners (especially with larger radii) and arcs in TikZ do not always go well together. In general, the exact same path with rounded corners applied does not exactly overlap the other path that does not use rounded corners. I assume that this is because the rounded corner hits an arc.

Is there a feasible way to get such rounded corners (preferably with different radii) without this distorting effect? I already tried to shorten the arc or temporarily switch off rounded corners, but I still often get rounded corners that don’t nicely merge with the arc and I don’t really like to cobble together the path like this.

\documentclass[border=5pt,tikz]{standalone} 

\usetikzlibrary{spy}

\begin{document} 

\begin{tikzpicture}[
  pshift/.style args={(#1:#2)}{%
    shift={({sin(-#1)*#2},{cos(-#1)*#2})}
  },
  line/.style={%
    line width=5pt
  },
  spy using outlines={%
    circle,
    magnification=5, 
    size=2cm, 
    connect spies
  }
]

\begin{scope}[opacity=.5]
\draw[line, green!75!black] 
    (30:3cm) 
    -- (30:6cm) 
    arc (30:60:6cm) 
    -- (60:9cm) 
    arc (60:90:9cm) 
    -- (90:3cm);

\draw[line, green!50!black]
    ([pshift={(30:10pt)}]30:3cm) 
    -- ([pshift={(30:10pt)}]30:{6cm-10pt}) 
    arc ({30+asin(10pt/(6cm-10pt))}:{60+asin(10pt/(6cm-10pt))}:{6cm-10pt}) 
    -- ([pshift={(60:10pt)}]60:{9cm-10pt}) 
    arc ({60+asin(10pt/(9cm-10pt))}:{90-asin(10pt/(9cm-10pt))}:{9cm-10pt}) 
    -- ([pshift={(90:-10pt)}]90:3cm);
\end{scope}

\draw[line, red!75!black, rounded corners=12pt] 
    (30:3cm) 
    -- (30:6cm) 
    [rounded corners=4pt]
    arc (30:60:6cm) 
    [rounded corners=12pt]
    -- (60:9cm) 
    arc (60:90:9cm) 
    -- (90:3cm);

\draw[line, red!50!black, rounded corners=4pt] 
    ([pshift={(30:10pt)}]30:3cm) 
    -- ([pshift={(30:10pt)}]30:{6cm-10pt}) 
    [rounded corners=12pt]
    arc ({30+asin(10pt/(6cm-10pt))}:{60+asin(10pt/(6cm-10pt))}:{6cm-10pt}) 
    [rounded corners=4pt]
    -- ([pshift={(60:10pt)}]60:{9cm-10pt}) 
    arc ({60+asin(10pt/(9cm-10pt))}:{90-asin(10pt/(9cm-10pt))}:{9cm-10pt}) 
    -- ([pshift={(90:-10pt)}]90:3cm);

\spy on (57.5:{6cm-10pt}) in node at (60:4cm);

\spy on (85:9cm) in node at (75:7cm);

\end{tikzpicture}

\end{document}

enter image description here


An example with temporarily disabled rounded corners, better but still a bit off:

\documentclass[border=5pt,tikz]{standalone} 

\usetikzlibrary{spy}

\begin{document} 

\begin{tikzpicture}[
  pshift/.style args={(#1:#2)}{%
    shift={({sin(-#1)*#2},{cos(-#1)*#2})}
  },
  line/.style={%
    line width=5pt
  },
  spy using outlines={%
    circle,
    magnification=5, 
    size=2cm, 
    connect spies
  }
]

\begin{scope}[opacity=.5]
\draw[line, green!75!black] 
    (60:6cm) 
    -- (60:9cm) 
    arc (60:90:9cm) 
    -- (90:6cm);
\end{scope}

\draw[line, blue!75!black, rounded corners=12pt] 
    (60:6cm) 
    -- (60:9cm) 
    [rounded corners=0pt]
    -- ([pshift={(60:12pt)}]60:9cm)
    arc (({60+asin(12pt/(9cm-12pt))}:({90-asin(12pt/(9cm-12pt))}:9cm) 
    [rounded corners=12pt]
    -- (90:9cm)
    -- (90:6cm);

\spy on (85:9cm) in node at (75:7cm);

\spy on (65:9cm) in node at (75:11cm);

\end{tikzpicture}

\end{document}

enter image description here

3
  • 1
    Made an update to my answer that creates a new key. Has some issues but is much easier to use. Just \draw[newrounded corners=10mm] will work.
    – Sandy G
    Mar 21, 2021 at 18:41
  • 1
    Your update only works with closed paths. Otherwise, it is a nice idea! I will still stick to your first approach, because I need the rounded corners to have different radii on the same path. Mar 21, 2021 at 20:12
  • 1
    I noticed that I was calculating the intersection points twice. Fixed the code in my answer. Now it's almost twice as fast.
    – Sandy G
    Mar 23, 2021 at 14:20

2 Answers 2

17
+50

The problem is not really about the arc command. It is about how TikZ shortens any curved path. Using arc or controls or to[in= , out=] all produce the same problem.

Consider the image:

enter image description here

which is created with the code

\begin{tikzpicture}[scale=10]
\draw[line width=5mm, color=green!75!black] (0,0)--(30:1) arc(30:80:1) -- cycle;
\draw[line width=5mm, color=red!75!black, rounded corners=10mm] (0,0)--(30:1) arc(30:80:1) -- cycle;
\draw[line width=5mm, color=blue!75!black, shorten >=10mm, shorten <=10mm] (30:1) arc(30:80:1);
\end{tikzpicture}

The green path is two segments and an arc. The red path is identical but with rounded corners, creating the undesired displacement. The blue path is simply the arc but shortened by 10mm on both ends, illustrating what goes wrong with rounded corners. Using rounded corners simply shortens the path—the blue and red paths are identical.

So instead of shortening the arc, we need to erase part of it. This image illustrates the idea:

enter image description here

The circles (drawn here in black) are centered at the corner we want to round. They are used to calculate intersection points on the original path (using the intersections library). Portions of the path are then erased (covered in yellow here) and replaced with an ordinary rounded corners path (blue) drawn through the original corner between the two intersection points. Round line caps are used to make a smooth connection.

This is all put into a macro \roundarc that takes 2 arguments:

\newcommand{\roundarc}[2]{\path[name path=C1] (#1) circle[radius=#2];  
    \draw[name intersections={of=C1 and P1, by={a1,a2}}, double distance=0, color=white] (a1)--(#1)--(a2);
    \draw[rounded corners=#2, line cap=round] (a1)--(#1)--(a2);}

The first argument is the coordinate without parentheses of the corner to be rounded. The second is the radius of the rounding. The original path must be named P1. The circles are not drawn. The covering lines are drawn in white.

Then the code

\begin{tikzpicture}[line width=5mm, color=red!75!black]
\draw[color=green!75!black] (0,0)--(30:10) arc(30:80:10) -- cycle;
\draw[name path=P1] {[rounded corners=10mm] (80:10) -- (0,0)} -- (30:10) arc(30:80:10);
\roundarc{80:10}{10mm}
\roundarc{30:10}{10mm}
\end{tikzpicture}

creates the desired image.

enter image description here

Note that the green arc is drawn first but is completely hidden. Also note that the corner formed by the two straight segments is rounded using ordinary rounded corners.

Here is the complete code:

\documentclass{article} 

\usepackage{tikz}
\usetikzlibrary{intersections}

\newcommand{\roundarc}[2]{\path[name path=C1] (#1) circle[radius=#2];  
    \draw[name intersections={of=C1 and P1, by={a1,a2}}, double distance=0, color=white] (a1)--(#1)--(a2);
    \draw[rounded corners=#2, line cap=round] (a1)--(#1)--(a2);}

\begin{document}

\begin{tikzpicture}[line width=5mm, color=red!75!black]
\draw[color=green!75!black] (0,0)--(30:10) arc(30:80:10) -- cycle;
\draw[name path=P1] {[rounded corners=10mm] (80:10) -- (0,0)} -- (30:10) arc(30:80:10);
\roundarc{80:10}{10mm}
\roundarc{30:10}{10mm}
\end{tikzpicture}

\end{document}

Update:

For closed paths, this can all be made into a new key. We can define newrounded corners so that the code

\draw[newrounded corners=10mm] (0,0)--(30:8) arc(30:80:8) -- cycle;

will produce the desired image. (Transparent original path is overlaid.) There are a few issues noted at the end.

enter image description here

Here is the code:

\documentclass{article} 

\usepackage{tikz}
\usetikzlibrary{intersections, decorations.pathreplacing}

\tikzset{
    newrounded corners/.style={
        name path=P1,
        decoration={
            show path construction,
            lineto code={
                \path[name path=C1] (\tikzinputsegmentlast) circle[radius=#1];
                \draw[name intersections={of=C1 and P1, by={a1,a2}}, double distance=0, color=white] (a1)--(\tikzinputsegmentlast)--(a2);
                \draw[rounded corners=#1, line cap=round] (a1)--(\tikzinputsegmentlast)--(a2);
            },
            curveto code={
                \path[name path=C1] (\tikzinputsegmentlast) circle[radius=#1];
                \draw[name intersections={of=C1 and P1, by={a1,a2}}, double distance=0, color=white] (a1)--(\tikzinputsegmentlast)--(a2);
                \draw[rounded corners=#1, line cap=round] (a1)--(\tikzinputsegmentlast)--(a2);
            },
            closepath code={
                \path[name path=C1] (\tikzinputsegmentlast) circle[radius=#1];
                \draw[name intersections={of=C1 and P1, by={a1,a2}}, double distance=0, color=white] (a1)--(\tikzinputsegmentlast)--(a2);
                \draw[rounded corners=#1, line cap=round] (a1)--(\tikzinputsegmentlast)--(a2);
            },
        },
        postaction={decorate}
    },
    newrounded corners/.default=4mm
}

\begin{document}

\begin{tikzpicture}[line width=5mm, color=red!75!black]
\draw[newrounded corners=10mm] (0,0)--(30:8) arc(30:80:8) -- cycle;
\draw[opacity=.3] (0,0)--(30:8) arc(30:80:8) -- cycle;
\end{tikzpicture}

\end{document}

Known issues:

  • Unexpected results if the rounding radius is too small relative to the line width.
  • The white path that covers the original might also cover other parts of the tikz picture.
5
  • 1
    +1: Very good answer! Mar 20, 2021 at 5:35
  • This is indeed a very nice answer! Thank you for explaining the problem! I was not aware of the problem being a result of shortened paths. Mar 20, 2021 at 8:13
  • 1
    Such a smart solution within such a small amount of code! Amazing!
    – SebGlav
    Mar 20, 2021 at 9:38
  • 1
    One question though: If we are to draw this over something else, the white parts would be visible. Wouldn't it be possible to do the same thing by clipping out those hiding parts before drawing, then draw the rounded corners above?
    – SebGlav
    Mar 20, 2021 at 9:44
  • 1
    @SebGlav As far as I know, inverse clipping is not too easy to achieve and it also has to be applied on the original path. I think, it is quite difficult to implement here, at least in the second approach. Mar 21, 2021 at 22:11
10

Sandy G has provided an excellent analysis of the issue and a good solution. This is an alternative solution that results in a single path at the end, which is therefore more suited to situations in which that is a consideration, such as if the region is to be filled.

It uses my spath3 TikZ library, and I've just added some extras needed to achieve this, so at time of writing this needs the development version (download spath3_code.dtx from that repository and run tex spath3_code.dtx to generate the files). Once it has been fully tested, I'll upload it to CTAN.

Here's the code:

\documentclass{article}
%\url{https://tex.stackexchange.com/q/588089/86}

\usepackage{tikz}
\usetikzlibrary{spath3}

\begin{document}
\begin{tikzpicture}[scale=10]
\draw[line width=5mm, color=green!75!black,spath/save=wedge] (0,0)--(30:1) arc(30:80:1) -- cycle;
\draw[line width=5mm, color=red!75!black, rounded corners=10mm] (0,0)--(30:1) arc(30:80:1) -- cycle;
\draw[line width=5mm, color=blue!75!black, shorten >=10mm, shorten <=10mm] (30:1) arc(30:80:1);
\tikzset{
  spath/insert gaps after segments={wedge}{20mm},
  spath/join components with bezier={wedge},
  spath/close with curve={wedge}
}
\draw[
  fill=white,
  fill opacity=.5,
  ultra thick,spath/use={wedge}];
\end{tikzpicture}
\end{document}

And the result:

Rounded corners achieved with spath3

It works as follows. The original path is split into segments (which are minimal drawing pieces). Each segment is shortened along its length (unlike TikZ's usual shortening routine, this is guaranteed to be along the original path). Then the segments are re-combined by inserting a Bézier curve in the gaps. The curves are designed to match the tangents at either end of the gap.

I chose Béziers rather than arcs for a couple of reasons. One being practicality - at the lower level all curved paths in TikZ are Bézier curves - and the other being that calculating an appropriate arc is potentially quite complicated as it would affect how much the curves had to be shortened as well (so you couldn't just shorten the curves and then insert an arc). The greater flexibility of Bézier curves avoids that issue.

3
  • Would the downvoter care to explain what I could do to improve this answer or in what way it isn't a suitable answer to this question? Mar 23, 2021 at 20:47
  • 1
    Sometimes downvotes just don't make any sense. This is a great solution. +1
    – Sandy G
    Mar 23, 2021 at 22:00
  • @SandyG Agreed, but even after all these years on the site it still rankles to get a down-vote! Anyway, thanks for the +1. I'd re-iterate but I've already voted for your answer and can't vote for it twice! Mar 23, 2021 at 23:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.