# padded boundary of convex hull

I'm trying to draw a boundary around a convex set of circular nodes in TikZ. The aim is to have it padded by 1cm, with an arc at the corners between segments, like this: I currently have the following code:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}

\begin{document}
\begin{tikzpicture}[every node/.style={circle,draw=blue}]

\node at (0,0) (a) {};
\node at (1,3) (b) {};
\node at (2,2) (c) {};

\draw[red]
($(a)!1cm!-90:(c)$)
let \p1 = ($(a)!1cm!-90:(c) - (a)$),
\n1 = {atan2(\x1,\y1)},
\p2 = ($(a)!1cm!90:(b) - (a)$),
\n2 = {atan2(\x2,\y2)},
\n{delta} = {-Mod(\n1-\n2,360)}
in
arc [start angle=\n1, delta angle=\n{delta}, radius=1cm]
--
($(b)!1cm!-90:(a)$)
let \p1 = ($(b)!1cm!-90:(a) - (b)$),
\n1 = {atan2(\x1,\y1)},
\p2 = ($(b)!1cm!90:(c) - (b)$),
\n2 = {atan2(\x2,\y2)},
\n{delta} = {-Mod(\n1-\n2,360)}
in
arc [start angle=\n1, delta angle=\n{delta}, radius=1cm]
--
($(c)!1cm!-90:(b)$)
let \p1 = ($(c)!1cm!-90:(b) - (c)$),
\n1 = {atan2(\x1,\y1)},
\p2 = ($(c)!1cm!90:(a) - (c)$),
\n2 = {atan2(\x2,\y2)},
\n{delta} = {-Mod(\n1-\n2,360)}
in
arc [start angle=\n1, delta angle=\n{delta}, radius=1cm]
-- cycle;

\end{tikzpicture}
\end{document}


I'm going to have to draw a lot of these, so it would useful if I could replace the \draw statement by something like:

\draw[red] \convexpath{(a) (b) (c)}{1cm};


where (a) (b) (c) are the nodes in the corners of the convex set in a clockwise direction. In the case of one node I would like it to draw a circle of the specified radius around the node in question.

Does anyone have any suggestions for how to go about writing such a macro?

• Do you want to be able to specify four or more nodes, some of which might not influence the hull?
– Jake
Sep 1, 2011 at 13:31
• No, you can assume the hull is known, and that the nodes are given in a clockwise direction. Sep 1, 2011 at 13:36
• I tried to compile both examples from above, always pdflatex reported for instance: Package pgf Error: No shape named is known \draw[red]\convexpath{a,b,c,d}{1cm} What trick am I missing? It can't be so difficult ... I am using Debian, is it a version problem?
– user25147
Jan 28, 2013 at 19:15
• @curiosity: Which version of PGF are you using (check your .log file)
– Jake
Jan 29, 2013 at 8:52

In case anyone is interested, this is a small modification to Jake's answer: it simplifies a few of the calculations, and now works for a single node (in which case it draws a circle):

\newcommand{\convexpath}{
[
create hullcoords/.code={
\global\edef\namelist{#1}
\foreach [count=\counter] \nodename in \namelist {
\global\edef\numberofnodes{\counter}
\coordinate (hullcoord\counter) at (\nodename);
}
\coordinate (hullcoord0) at (hullcoord\numberofnodes);
\pgfmathtruncatemacro\lastnumber{\numberofnodes+1}
\coordinate (hullcoord\lastnumber) at (hullcoord1);
},
create hullcoords
]
($(hullcoord1)!#2!-90:(hullcoord0)$)
\foreach [
evaluate=\currentnode as \previousnode using \currentnode-1,
evaluate=\currentnode as \nextnode using \currentnode+1
] \currentnode in {1,...,\numberofnodes} {
let \p1 = ($(hullcoord\currentnode) - (hullcoord\previousnode)$),
\n1 = {atan2(\x1,\y1) + 90},
\p2 = ($(hullcoord\nextnode) - (hullcoord\currentnode)$),
\n2 = {atan2(\x2,\y2) + 90},
\n{delta} = {Mod(\n2-\n1,360) - 360}
in
{arc [start angle=\n1, delta angle=\n{delta}, radius=#2]}
-- ($(hullcoord\nextnode)!#2!-90:(hullcoord\currentnode)$)
}
}


Note if using the pgf/tikz >=3.0, you need to switch the arguments of atan2 (so atan2(\x1,\y1) becomes atan2(\y1,\x1). If you need to support multiple versions (for working with collaborators, uploading to arxiv, etc.) you can use \@ifpackagelater, e.g.

\@ifpackagelater{tikz}{2013/12/01}{...}{...}

• That's great! You should probably accept this answer instead of mine (I think you might need to wait a bit before that's possible) so it will be more obvious to others looking for a way to do this.
– Jake
Sep 15, 2011 at 12:05
• When defining \previousnode and \nextnode, you should write int(\currentnode +/- 1). Otherwise, hullcoord\nextnode will expand to e.g. hullcoord0.0 causing small bad visible flickers in the result. Btw, I would also prefer if this was the accepted and thus topmost answer.
– gTcV
Oct 23, 2014 at 11:08
• Also, be careful to change atan2(\x2,\y2) to atan2(\y2,\x2).
– mrm
Mar 17, 2015 at 21:03

Here's one way of doing it: I used [<new pgf key>/.code={...}, <new pgf key>] to smuggle some code into the TikZ path that can be executed without interrupting the path construction. The code creates new invisible nodes at the position of all the specified nodes and names them sequentially (hullnode[1...n]), and then creates two additional nodes hullnode0 at hullnode[n] and hullnode[n+1] at hullnode1. This makes it possible to just loop over hullnode[1...n] and draw the arcs.

The command takes two arguments: A comma separated list of node names (without parentheses), and a buffer length. If anyone succeeds in making the buffer size argument optional, please tell me how!

The code

  \node at (0,0) (a) {};
\node at (2,3) (b) {};
\node at (3,-1) (c) {};
\node at (1,-2) (d) {};

\draw[red] \convexpath{a,b,c,d}{1cm};
\draw[thick,blue] \convexpath{a,c,d}{1.2cm};


will produce Note that for pgf<3.0.0, you have to switch the arguments of atan2 below, i.e. atan2(\y2,\x2) should be atan2(\x2,\y2) both times it appears. If the code seems not to work correctly, try making this change.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}

\newcommand{\convexpath}{
[
create hullnodes/.code={
\global\edef\namelist{#1}
\foreach [count=\counter] \nodename in \namelist {
\global\edef\numberofnodes{\counter}
\node at (\nodename) [draw=none,name=hullnode\counter] {};
}
\node at (hullnode\numberofnodes) [name=hullnode0,draw=none] {};
\pgfmathtruncatemacro\lastnumber{\numberofnodes+1}
\node at (hullnode1) [name=hullnode\lastnumber,draw=none] {};
},
create hullnodes
]
($(hullnode1)!#2!-90:(hullnode0)$)
\foreach [
evaluate=\currentnode as \previousnode using \currentnode-1,
evaluate=\currentnode as \nextnode using \currentnode+1
] \currentnode in {1,...,\numberofnodes} {
-- ($(hullnode\currentnode)!#2!-90:(hullnode\previousnode)$)
let \p1 = ($(hullnode\currentnode)!#2!-90:(hullnode\previousnode) - (hullnode\currentnode)$),
\n1 = {atan2(\y1,\x1)},
\p2 = ($(hullnode\currentnode)!#2!90:(hullnode\nextnode) - (hullnode\currentnode)$),
\n2 = {atan2(\y2,\x2)},
\n{delta} = {-Mod(\n1-\n2,360)}
in
{arc [start angle=\n1, delta angle=\n{delta}, radius=#2]}
}
-- cycle
}

\begin{document}
\begin{tikzpicture}[every node/.style={circle,draw=blue}]
\node at (0,0) (a) {};
\node at (2,3) (b) {};
\node at (3,-1) (c) {};
\node at (1,-2) (d) {};

\draw[red] \convexpath{a,b,c,d}{1cm};
\draw[thick,blue] \convexpath{a,c,d}{1.2cm};

\end{tikzpicture}
\end{document}

• Unfortunately, the result is no longer the same as of pgf 3.0.0. Feb 1, 2014 at 23:22
• @Raim I just came across exactly the same problem: it seems that the atan2 function has changed, so you need to switch the arguments: e.g. atan2(\x1,\y1) becomes atan2(\y1,\x1) Mar 21, 2014 at 11:35
• @SimonByrne good catch. I already wasn't understanding why my old presentations would no longer compile correctly. Perhaps this can also be changed in the code provided above by @Jake? Nov 18, 2014 at 14:28
• @SimonByrne Thanks so much. I guess it really does pay to keep stackexchange links as comments in your sources ;-) May 3, 2015 at 16:09

Here's an alternative solution. Not as fancy as the other two answers, but simpler to use I think, plus you can more easily customize the borders. Also, it doesn't matter whether the order is clockwise or counter-clockwise.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{backgrounds}
\usetikzlibrary{calc}
\usepackage[active,tightpage]{preview}
\PreviewEnvironment{tikzpicture}
\setlength\PreviewBorder{5mm}
\begin{document}
\begin{tikzpicture}
\tikzstyle{n}=[draw,circle,minimum size=10mm];
\node[n] at (0,0) (A) {A};
\node[n] at (2,0.5) (B) {B};
\node[n] at (2,2.5) (C) {C};
\node[n] at (1,3) (D) {D};

\begin{pgfonlayer}{background}
% Create coordinate place holders
\foreach \nodename in {A,B,C,D} {
\coordinate (\nodename') at (\nodename);
}
\path[fill=blue!50,draw=blue!50,line width=1.4cm, line cap=round, line join=round]
(A') to[bend left=20] (B')
to[bend left=50] (C')
to (D')
to[bend left] (A') -- cycle;
\end{pgfonlayer}

\end{tikzpicture}

\end{document}


If instead you only want the line, you can use the same approach as for the double line style. We simply draw the same path again, but a little bit smaller and in the background color (white in this case).

\path[fill=blue!50,draw=blue!50,line width=1.4cm, line cap=round, line join=round]
(A') to[bend left=20] (B')
to[bend left=50] (C')
to (D')
to[bend left] (A') -- cycle;
\path[fill=white,draw=white,line width=1.3cm, line cap=round, line join=round]
(A') to[bend left=20] (B')
to[bend left=50] (C')
to (D')
to[bend left] (A') -- cycle;


This is what it looks like with fill and as line:  • Some thoughts: Adding Output.png in answer will surely make it more attractive and catchy for upvoters and equally comparable with other answers. Mar 25, 2013 at 13:11
• Yes, I tried to to it yesterday, but I couldn't upload the figures, as I didn't have enough reputation points. I've uploaded them now (apparently not under my own account). Mar 26, 2013 at 13:29
• Thanks for these ideas. By the way, I noticed that when drawing on the background layer, using (nodename.center) as coordinates may allow you to get rid of the Create coordinate place holders part: the edges will be drawn all the way to the center of the node, giving the same result. It worked in my case, at least, and it made my commands slightly cleaner. Jul 31, 2017 at 11:46

The following (a bit cleaner I hope) code is from jackal's answer without using some manually calculating options such as [bend left=20]. \documentclass[tikz,border=5mm]{standalone}
\begin{document}
\begin{tikzpicture}
\tikzstyle{n}=[draw,circle,minimum size=10mm];
\path
(0,0) coordinate (A)
(5,0) coordinate (B)
(3,3) coordinate (C)
(1,4) coordinate (D);

\path[draw=blue!50,fill=blue!50,line width=1.4cm,line cap=round,line join=round]
(A)--(B)--(C)--(D)--cycle
(A)--(C) (B)--(D);

\path
(A) node[n]{A}
(B) node[n]{B}
(C) node[n]{C}
(D) node[n]{D};
\end{tikzpicture}

\begin{tikzpicture}
\tikzstyle{n}=[draw,circle,minimum size=10mm];
\path
(0,0) coordinate (A)
(5,0) coordinate (B)
(3,3) coordinate (C)
(1,4) coordinate (D);

\path[fill=blue!50,draw=blue!50,line width=1.4cm,line cap=round,line join=round]
(A)--(B)--(C)--(D)--cycle
(A)--(C) (B)--(D);

\path[fill=white,draw=white,line width=1.3cm,line cap=round,line join=round]
(A)--(B)--(C)--(D)--cycle
(A)--(C) (B)--(D);

\path
(A) node[n]{A}
(B) node[n]{B}
(C) node[n]{C}
(D) node[n]{D};
\end{tikzpicture}
\end{document} 