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.

Motivation

In the answer Highlight a group of nodes in a tikz tree, Jake suggested combining the convex hull approach from padded boundary of convex hull with the hobby path and I was really intrigued by the possibility.

Preliminary work

At first I tried to modify at least as possible the \convexpath:

\documentclass[a4paper,11pt]{article}
\usepackage{tikz}
\usetikzlibrary{hobby,backgrounds,calc,trees}

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

\begin{document}
\begin{tikzpicture}[use Hobby shortcut]
\node (f) {f}
    child { node (g) {g}
      child { node (a) {a}
    }
      child { node (b) {b}
    }
  }
    child { node (h) {h}
      child { node (c) {c}
    }
  };
  \begin{pgfonlayer}{background}
  \fill[draw,blue, opacity=0.3] \myconvexpath{f,h,c,g}{12pt};
  \fill[draw,red, opacity=0.3] \myconvexpath{g,b,a}{12pt};
  \end{pgfonlayer}
  \end{tikzpicture}

\end{document}

which leads to:

enter image description here

I suspected the combination of arcs with the hobby path was the cause of cusps, so in another example I tried with:

\documentclass[a4paper,11pt]{article}
\usepackage{tikz}
\usetikzlibrary{hobby,backgrounds,calc,trees}

\newcommand{\myconvexpath}[2]{
[   
    create hobbyhullnodes/.code={
        \global\edef\namelist{#1}
        \foreach [count=\counter] \nodename in \namelist {
            \global\edef\numberofnodes{\counter}
            \node at (\nodename) [draw=none,name=hobbyhullnode\counter] {};
        }
        \node at (hobbyhullnode\numberofnodes) [name=hobbyhullnode0,draw=none] {};
        \pgfmathtruncatemacro\lastnumber{\numberofnodes+1}
        \node at (hobbyhullnode1) [name=hobbyhullnode\lastnumber,draw=none] {};
    },
    create hobbyhullnodes
]
($(hobbyhullnode1)!#2!-90:(hobbyhullnode0)$)
\foreach [
    evaluate=\currentnode as \previousnode using \currentnode-1,
    evaluate=\currentnode as \nextnode using \currentnode+1
    ] \currentnode in {1,...,\numberofnodes} {
   let \p1 = ($(hobbyhullnode\currentnode)!#2!-90:(hobbyhullnode\previousnode)$),
    \n1 = {atan2(\x1,\y1)},
    \p2 = ($(hobbyhullnode\currentnode)!#2!90:(hobbyhullnode\nextnode)$),
    \n2 = {atan2(\x2,\y2)},
    \n{delta} = {-Mod(\n1-\n2,360)},
    \n{end}={add(\n1,\n{delta})}
  in 
    {..([in angle=\n1]$(hobbyhullnode\currentnode)!#2!-90:(hobbyhullnode\previousnode)$)..([out angle=\n{end}]$(hobbyhullnode\currentnode)!#2!90:(hobbyhullnode\nextnode)$)}
     ..($(hobbyhullnode\nextnode)!0.5!(hobbyhullnode\currentnode)$)
     ..($(hobbyhullnode\nextnode)!#2!-90:(hobbyhullnode\currentnode)$)
}
--cycle
}

\begin{document}
\begin{tikzpicture}[use Hobby shortcut]
\node (f) {f}
    child { node (g) {g}
      child { node (a) {a}
    }
      child { node (b) {b}
    }
  }
    child { node (h) {h}
      child { node (c) {c}
    }
  };
  \begin{pgfonlayer}{background}
  \fill[draw,blue, opacity=0.3] \myconvexpath{f,h,c,g}{12pt};
  \fill[draw,red, opacity=0.3] \myconvexpath{g,b,a}{12pt};
  \end{pgfonlayer}
  \end{tikzpicture}

\end{document}

that gives a not promising result:

enter image description here

Question

Is there a way to automatically recognize the node angle a path will fall when arrives near it? Doing things by hand, one can force a path to follow the desired direction, for example, h.north -> h.east -> h.south, but how is it possible to do it automatically without the arc syntax?

Notice that, for some shapes, one could proceed as follows:

\documentclass[a4paper,11pt]{article}
\usepackage{tikz}
\usetikzlibrary{hobby,backgrounds,calc,trees}

\newcommand{\hobbyconvexpath}[2]{
[   
    create hobbyhullnodes/.code={
        \global\edef\namelist{#1}
        \foreach [count=\counter] \nodename in \namelist {
            \global\edef\numberofnodes{\counter}
            \node at (\nodename) [draw=none,name=hobbyhullnode\counter] {};
        }
        \node at (hobbyhullnode\numberofnodes) [name=hobbyhullnode0,draw=none] {};
        \pgfmathtruncatemacro\lastnumber{\numberofnodes+1}
        \node at (hobbyhullnode1) [name=hobbyhullnode\lastnumber,draw=none] {};
    },
    create hobbyhullnodes
]
($(hobbyhullnode1)!#2!-40:(hobbyhullnode0)$)
\foreach [
    evaluate=\currentnode as \previousnode using \currentnode-1,
    evaluate=\currentnode as \nextnode using \currentnode+1
    ] \currentnode in {1,...,\numberofnodes} {
  let \p1 = ($(hobbyhullnode\currentnode)!#2!-90:(hobbyhullnode\previousnode) $),
    \n1 = {atan2(\x1,\y1)},
    \p2 = ($(hobbyhullnode\currentnode)!#2!-90:(hobbyhullnode\nextnode)$),
    \n2 = {atan2(\x2,\y2)},
    \n{delta} = {-Mod(\n1-\n2,360)},
    \n{fin}={add(\n1,\n{delta})}
  in 
      {..($(hobbyhullnode\currentnode)!#2!-220:(hobbyhullnode\previousnode)$)..($(hobbyhullnode\currentnode)!#2!40:(hobbyhullnode\nextnode)$)}
   %{arc [start angle=\n1, end angle=\n{fin}, radius=#2]}
     ..($(hobbyhullnode\nextnode)!0.5!(hobbyhullnode\currentnode)$)
     ..($(hobbyhullnode\nextnode)!#2!-40:(hobbyhullnode\currentnode)$)
}
--cycle
}

\begin{document}

\begin{tikzpicture}[use Hobby shortcut]
  \foreach \place/\text in {{(1,0)/a},{(0,-1)/b},{(-1,0)/c},{(0,1)/d}}
  \node[name=\text] at \place {\text};
  \begin{pgfonlayer}{background}
  \fill[draw,green, opacity=0.3] \hobbyconvexpath{a,b,c,d}{10pt};
  \end{pgfonlayer}
\end{tikzpicture}

\end{document}

enter image description here

but in general is not a valid approach and it is still to improve, to get at least the same result of Highlight a group of nodes in a tikz tree.

share|improve this question
    
I'm not clear as to the role of Hobby's algorithm here. That produces a passably smooth curve through a given set of points. It is designed for when you don't know much about the curve other than those points. Here, it would seem that you know too much about the curve for Hobby's algorithm to be of particular use. –  Andrew Stacey Sep 17 '12 at 12:37
    
@AndrewStacey: in your opinion a classical curve path construction (pos)..controls (controll point)and(controll point).. would give similar results? Actually, when I answered in Highlight a group of nodes in a tikz tree I didn't considered the possibility... –  Claudio Fiandrino Sep 17 '12 at 17:51
3  
What I suspect is that you'll want to put various constraints on the path and those will mean that the full power of Hobby's algorithm isn't used (there might be some use for computing the control points given the angles). So I'd want to start by knowing what makes a "good" diagram and what characteristics it has. If you'd like to discuss it, perhaps the "From Answers to Packages" chat room would be a good place. –  Andrew Stacey Sep 17 '12 at 19:37
add comment

1 Answer

up vote 29 down vote accepted

I'm answering because I think there's a quite good solution on the problem. First of all I'd like to thank Andrew Stacey because without the discussion in chat I wouldn't be able to solve this and, more important, he founds the major issue in the code.


What I learnt

The answer to the question:

Is there a way to automatically recognize the node angle a path will fall when arrives near it?

is actually: TikZ already does it automatically. Notice indeed that:

\documentclass{article}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}
\node[circle,fill=red] (a) at (1,1) {}; 
\node[circle,fill=blue] (b) at  (3,2) {};
\draw[color=black] (a) -- (b);
\end{tikzpicture}
\end{document}

correctly leads to:

enter image description here

and the user should not have to care about the angle the line arrives near the blue node.

The major issue

Building on that, however, was not sufficient. Andrew recognized that the paths drawn were not correctly realized due to the fact that the hobby path shortcut construct separately each piece; this was the cause of the bizarre behaviour as per the second figure displayed in the question.

The intermediate result

With his help it was possible to get (code here):

enter image description here

and notice how this solved the problems mentioned before.

A final result

Starting from the intermediate result I noticed that actually the non-perfect fit around some nodes was basically due to the out angle, that is the the path from hypothetically ($(hobbyhullnode1)!10pt!-90:(hobbyhullnode0)$)..($(hobbyhullnode1)!10pt!90:(hobbyhullnode0)$). With a bit of care, the right angle should have been set to 180 rather than 90.

Here is a MWE:

\documentclass[tikz,border=2bp]{standalone}
\usetikzlibrary{backgrounds,calc,trees,hobby}

\pgfdeclarelayer{background}
\pgfsetlayers{background,main}

\newcommand{\hobbyconvexpath}[2]{
[   
    create hobbyhullnodes/.code={
        \global\edef\namelist{#1}
        \foreach [count=\counter] \nodename in \namelist {
            \global\edef\numberofnodes{\counter}
            \node at (\nodename)
[draw=none,name=hobbyhullnode\counter] {};
        }
        \node at (hobbyhullnode\numberofnodes)
[name=hobbyhullnode0,draw=none] {};
        \pgfmathtruncatemacro\lastnumber{\numberofnodes+1}
        \node at (hobbyhullnode1)
[name=hobbyhullnode\lastnumber,draw=none] {};
    },
    create hobbyhullnodes
]
($(hobbyhullnode1)!#2!-90:(hobbyhullnode0)$)
\pgfextra{
  \gdef\hullpath{}
\foreach [
    evaluate=\currentnode as \previousnode using \currentnode-1,
    evaluate=\currentnode as \nextnode using \currentnode+1
    ] \currentnode in {1,...,\numberofnodes} {
    \pgfmathtruncatemacro\thecurrentnode\currentnode
    \pgfmathtruncatemacro\thepreviousnode\previousnode
    \pgfmathtruncatemacro\thenextnode\nextnode
    \xdef\hullpath{\hullpath    
  ..($(hobbyhullnode\thecurrentnode)!#2!180:(hobbyhullnode\thepreviousnode)$)
  ..($(hobbyhullnode\thenextnode)!0.5!(hobbyhullnode\thecurrentnode)$)}
    \ifx\currentnode\numberofnodes
    \xdef\hullpath{\hullpath .. cycle}
    \else
    \xdef\hullpath{\hullpath
  ..($(hobbyhullnode\thenextnode)!#2!-90:(hobbyhullnode\thecurrentnode)$)}
    \fi
}
}
\hullpath
}

\begin{document}
\begin{tikzpicture}[use Hobby shortcut,scale=3,transform shape]
\node (f) {f}
    child { node (g) {g} 
      child { node (a) {a}
    }
      child { node (b) {b}
    }
  }
    child { node (h) {h}
      child { node (c) {c}
      }
  };

\begin{pgfonlayer}{background}
\fill[red,opacity=0.3] \hobbyconvexpath{a,g,b}{10pt};
\end{pgfonlayer}

\draw[blue,dashed]($(hobbyhullnode1)!10pt!-90:(hobbyhullnode0)$)--
($(hobbyhullnode1)!10pt!-90:(hobbyhullnode0)$)..($(hobbyhullnode1)!10pt!180:(hobbyhullnode0)$)..
($(hobbyhullnode2)!0.5!(hobbyhullnode1)$) ..
($(hobbyhullnode2)!10pt!-90:(hobbyhullnode1)$)..($(hobbyhullnode2)!10pt!180:(hobbyhullnode1)$)..($(hobbyhullnode3)!0.5!(hobbyhullnode2)$)..
($(hobbyhullnode3)!10pt!-90:(hobbyhullnode2)$)..($(hobbyhullnode3)!10pt!180:(hobbyhullnode2)$)
..($(hobbyhullnode4)!0.5!(hobbyhullnode3)$) .. cycle;

\begin{pgfonlayer}{background}
\fill[green!50!lime,opacity=0.4] \hobbyconvexpath{g,f,h,c}{10pt};
\end{pgfonlayer}

\draw[blue,dashed]($(hobbyhullnode1)!10pt!-90:(hobbyhullnode0)$)--
($(hobbyhullnode1)!10pt!-90:(hobbyhullnode0)$)..($(hobbyhullnode1)!10pt!180:(hobbyhullnode0)$)..
($(hobbyhullnode2)!0.5!(hobbyhullnode1)$) ..
($(hobbyhullnode2)!10pt!-90:(hobbyhullnode1)$)..($(hobbyhullnode2)!10pt!180:(hobbyhullnode1)$)..($(hobbyhullnode3)!0.5!(hobbyhullnode2)$)..
($(hobbyhullnode3)!10pt!-90:(hobbyhullnode2)$)..($(hobbyhullnode3)!10pt!180:(hobbyhullnode2)$)
..($(hobbyhullnode4)!0.5!(hobbyhullnode3)$) ..
($(hobbyhullnode4)!10pt!-90:(hobbyhullnode3)$)..($(hobbyhullnode4)!10pt!180:(hobbyhullnode3)$)
..($(hobbyhullnode5)!0.5!(hobbyhullnode4)$) .. cycle;
\end{tikzpicture}

\begin{tikzpicture}[use Hobby shortcut,scale=3,transform shape]
\node (f) {f}
    child { node (g) {g} 
      child { node (a) {a}
    }
      child { node (b) {b}
    }
  }
    child { node (h) {h}
      child { node (c) {c}
      }
  };

\begin{pgfonlayer}{background}
\fill[orange,opacity=0.4] \hobbyconvexpath{a,g,h,b}{13pt};
\end{pgfonlayer}
\end{tikzpicture}

\begin{tikzpicture}[use Hobby shortcut,scale=3,transform shape]
\node (f) {f}
    child { node (g) {g} 
      child { node (a) {a}
    }
      child { node (b) {b}
    }
  }
    child { node (h) {h}
      child { node (c) {c}
      }
  };

\begin{pgfonlayer}{background}
\fill[cyan,opacity=0.4] \hobbyconvexpath{b,g,f,h}{10pt};
\end{pgfonlayer}
\end{tikzpicture}
\end{document}

with the following result:

enter image description here

Another example (since I noticed that the shape highlighted is actually the same in all the three previous figures):

\begin{tikzpicture}[use Hobby shortcut,scale=3,transform shape]
\node (f) {f}
    child { node (g) {g} 
      child { node (a) {a}
        child { node (i) {i}}
    }
      child { node (b) {b}
            child { node (d) {d}}
            child { node (e) {e}}
    }
  }
    child { node (h) {h}
      child { node (c) {c}
      }
  };

\begin{pgfonlayer}{background}
\fill[cyan,opacity=0.4] \hobbyconvexpath{a,g,f,b,e,d}{10pt};
\end{pgfonlayer}
\end{tikzpicture}

enter image description here

share|improve this answer
2  
Really nice!... –  Gonzalo Medina Oct 12 '12 at 16:19
    
What a nice surprise! –  Felipe Aguirre Oct 16 '12 at 20:19
    
The issue that lead to a non perfect alignment has been solved: I leave to the curious to check the code to understand what was the problem. :) –  Claudio Fiandrino Nov 9 '12 at 18:33
1  
If the edit was the culprit you can instead use evaluate=\currentnode as \previousnode using int(\currentnode-1) so that the result is truncated. I think that might be the same issue that was in tex.stackexchange.com/questions/52961/… –  percusse Nov 9 '12 at 20:45
1  
Note: see tex.stackexchange.com/q/121286/86 if using the latest version of the hobby package as the cycle path specification is (at time of writing) no longer valid. –  Andrew Stacey Jun 27 '13 at 9:00
show 3 more comments

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.