Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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:

convex set of nodes

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?

share|improve this question
    
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
add comment

3 Answers

up vote 31 down vote accepted

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}
share|improve this answer
    
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 at 23:22
2  
@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 at 11:35
add comment

In case anyone is interested, this is a small modification to Jake's code above: 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).

share|improve this answer
    
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
add comment

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:

Cover with fill Cover with line

share|improve this answer
    
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
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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