Show Source

In my work on Dyna, I very often need to draw directed B-hypergraphs. When drawing by hand, I almost invariably use (IMHO) nice, swooped arcs that join smoothly together. But the TeX tools like to draw straight lines, which result in bird-track-like hyperedges, which look pretty bad. Keep reading if you like pictures like this:

../../_images/tex-tikz-hyperedges-example1.png

TikZ/pgf, however, can do some amazing things. For my current paper, which uniformly draws circuits where sources are closer to the top of the page than targets, I have defined:

\usepackage{xparse}
\usepackage{xcolor}
\usepackage{tikz}
  \usetikzlibrary{calc,fit}

\makeatletter
\DeclareDocumentCommand{\fhe}{ O{} O{} D<>{} O{} m m }{
 \begin{scope}
  \coordinate (target) at (#5) ;

  \ifstrempty{#2}
    {\coordinate (fheoffset) at (0,.2) ; }
    {\coordinate (fheoffset) at #2 ;}

  \edef\fhe@midpoints{}
  \foreach \src in {#6} {\xdef\fhe@midpoints{($(\src)!0.5!(target)$)\fhe@midpoints}}
  \node [fit={(target) \fhe@midpoints}] (precrux) {};

  \ifstrempty{#4}{}{\coordinate (precrux) at (#4) ;}    % override precrux

  \coordinate (crux) at ($(precrux)!0.5!(target)$) ;

  \foreach \src in {#6}
    \draw [rounded corners, #1] (\src)
          .. controls ($(\src)-(fheoffset)$) .. ($(\src)!0.5!(precrux)$)
          .. controls ($(precrux)+(fheoffset)$) and (precrux) .. (crux) ;

  \draw [line width=.45pt, ->, #1] (crux) -- (target) ;

  \ifstrempty{#3}{}{\draw (crux) node #3} ;
 \end{scope}
}
\makeatother

This produces a series of curves from each source point to a “crux” where they all meet. In order to get the curves to look nice, a “precrux” is defined nearby and used in a control point calculations for these curves. By default, the precrux is computed by averaging the source positions and a heavy bias towards the target point. It is, however, adjustable by argument, in case the default doesn’t work out well for you. The crux is then the midpoint of the precrux and target; all the curves from the source converge here and a single line extends to the target.

As can be seen by the core \foreach loop, the hyperedges are drawn as curves from each source to the source-precrux midpoint to the crux, using some computed points to make things look pretty.

The parser directives produce a command that takes six arguments:

The use of \begin{scope} and \end{scope} results in none of the internal coordinates (crux, precrux, and target) being visible after the command completes.

All told, this lets me write things like the following:

\fhe{frs1.north}{r12.south,s2.south,f35.south}
\fhe[gray,dashed]{frs1.north}{r13.south,s3.south,f36.south}
\fhe<>[$(target)+(-.1,.1)$]{goal.north}{frs1.south}

to define three of the edges (the grey dashed one and the ones below it and to its left) in pictures like the one up top.