# 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 '11 at 13:31
No, you can assume the hull is known, and that the nodes are given in a clockwise direction. – Simon Byrne Sep 1 '11 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 '13 at 19:15
@curiosity: Which version of PGF are you using (check your .log file) – Jake Jan 29 '13 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}[2]{
[
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 '11 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 '14 at 11:08
Also, be careful to change atan2(\x2,\y2) to atan2(\y2,\x2). – mrm Mar 17 '15 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

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

\newcommand{\convexpath}[2]{
[
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(\x1,\y1)},
\p2 = ($(hullnode\currentnode)!#2!90:(hullnode\nextnode) - (hullnode\currentnode)$),
\n2 = {atan2(\x2,\y2)},
\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}

-
Brilliant, thanks! – Simon Byrne Sep 1 '11 at 15:16
Unfortunately, the result is no longer the same as of pgf 3.0.0. – Raim Feb 1 '14 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) – Simon Byrne Mar 21 '14 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? – jackal Nov 18 '14 at 14:28
@SimonByrne Thanks so much. I guess it really does pay to keep stackexchange links as comments in your sources ;-) – Joost May 3 '15 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. – texenthusiast Mar 25 '13 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). – jackal Mar 26 '13 at 13:29