# Drawing Tapered Edges in Graph

I'd like to achieve the following effect in a TikZ picture when drawing a straight edge from one node to another. How can I achieve this tapering effect? Notice what happens when an edge exists from A to B and also from B to A (but I suspect this will come for free). This image comes from:

www.win.tue.nl/~dholten/papers/directed_edges_chi.pdf

From "Wedged/tapering paths in TikZ", I was able to hack up something that looked decent, but I was curious if anyone could up with something better.

In particular, I'd like the direction of the tapering reversed. Or at least an API that would allow me to set the direction. Some people might want thick edges at the head and tapered edges at the tail. In the example I provide, I want the thick edge at the head, but the implementation is reversed, so I have to reverse all my edges. It would also be nice if you could specify edges as you do with the \path command:

\path (A) edge [tapered tail] (B);
\path (B) edge [tapered head] (C);
\path (A) edge [tapered tail, <-] (B); % effectively: tapered head


Here is the example:

\documentclass{minimal}
\usepackage{tikz}
\usetikzlibrary{decorations}

\begin{document}

% Need to reverse this!
\pgfdeclaredecoration{triangle}{start}{
\state{start}[width=0.99\pgfdecoratedinputsegmentremainingdistance,next state=up from center]
{\pgfpathlineto{\pgfpointorigin}}
\state{up from center}[next state=do nothing]
{
\pgfpathlineto{\pgfqpoint{\pgfdecoratedinputsegmentremainingdistance}{\pgfdecorationsegmentamplitude}}
\pgfpathlineto{\pgfqpoint{\pgfdecoratedinputsegmentremainingdistance}{-\pgfdecorationsegmentamplitude}}
\pgfpathlineto{\pgfpointdecoratedpathfirst}
}
\state{do nothing}[width=\pgfdecorationsegmentlength,next state=do nothing]{
\pgfpathlineto{\pgfpointdecoratedinputsegmentfirst}
\pgfpathmoveto{\pgfpointdecoratedinputsegmentlast}
}
}

\tikzset{
triangle path/.style={decoration={triangle,amplitude=#1}, decorate},
triangle path/.default=1ex}

\colorlet{mygray}{gray!50}

\pgfdeclarelayer{bg}    % declare background layer
\pgfsetlayers{bg,main}  % set the order of the layers (main is the standard layer)

\begin{tikzpicture}
\begin{scope}[circle]
\node[draw=gray,fill=mygray] at (0,0) {};
\node[draw=gray,fill=mygray] at (1,2) {};
\node[draw=gray,fill=mygray] at (.3,2.5) {};
\node[draw=gray,fill=mygray] at (0,-2) {};
\node[draw=gray,fill=mygray] at (-3,1) {};
\node[draw=gray,fill=mygray] at (3,1.5) {};
\end{scope}

\begin{pgfonlayer}{bg}
\begin{scope}[color=gray, opacity=.8]
\draw [fill=black!70, triangle path=.7ex] (1,2) -- (0,0);
\draw [fill=black!70, triangle path=.7ex] (.3,2.5) -- (0,0);
\draw [fill=black!70, triangle path=.7ex] (0,-2) -- (0,0);
\draw [fill=black!70, triangle path=.7ex] (0,0) -- (0,-2);
\draw [fill=black!70, triangle path=.7ex] (3,1.5) -- (-3,1);
\end{scope}
\end{pgfonlayer}

\end{tikzpicture}
\end{document}


The resulting image doesn't have quite the same polished look, perhaps its just because I don't have enough edges. Any improvements?

-
The missing polish, I think, comes from the transparency of the tapered edges. Otherwise they look identical. – percusse Feb 27 '13 at 18:58
You could use the transparency group option. What exactly does not look good? I think it looks just fine. – Qrrbrbirlbel Feb 27 '13 at 19:01
Ok, I'll give up trying to state what doesn't look good. What I'd still like to know is how to adjust my example into a better, more natural API. – Tom Feb 27 '13 at 19:41
@Tom Does it need to be a decoration? I’d just create a to path (especially good for edge). – Qrrbrbirlbel Feb 27 '13 at 19:58
@Qrrbrbirlbel I'm up for anything that makes this easier to do. I only used the decoration because I saw it as an answer (I don't actually understand the solution). – Tom Feb 27 '13 at 20:10

I used a to path that just draws a triangle.

Simple use is edge[tapered line=<dir>] where <dir> is one of >, < or x and specifies the direction of the triangle(s).

You can use the styles of the /tikz/tapered line/ tree to specify more (widths, style and opacity). These settings can also be used ad hoc on the edge, e.g.

edge[tapered line={>, source width=1ex, style={draw=none}, opacity=.5}]


It is possible to use either coordinates or nodes. When no anchor is given the .center one will used.

The same interface can be used with decorations, only the actual path is just (\tikztostart) -- (\tikztotarget) that is decorated.

Both possible paths (to and from) are enclosed in a scope with a transparency group that renders both the drawn line and the filled area better. The following picture shows this:

The line going from left to right does not use the transparency group and shows the overlapping of line and area.

On that note, I think a line that is tapered in both directions, an opacity of 50 % shall be used. Otherwise you will see that one is above the other.

One could these 50 % in combination with a darker or lighter color or another transparency group (only lighter).

I’m open for (graphical) ideas for x-tapered lines. One could clip the halves. One could draw both parts in two different paths: one for the inner lines and one for the outer lines. …

## Code

\documentclass[tikz]{standalone}
\usetikzlibrary{decorations.pathmorphing}
\makeatletter
\newif\if@qrr@tikz@taperedline@to@
\newif\if@qrr@tikz@taperedline@from@
\def\qrr@pgfutil@add@anchor#1#2{% ... if node is without anchor
\qrr@pgfutil@in@,{#1}% -> coordinate
\ifpgfutil@in@\else
\qrr@pgfutil@in@.{#1}% -> already node with anchor
\ifpgfutil@in@\else
\edef#1{#1#2}\fi\fi}
\def\qrr@pgfutil@in@#1#2{% to save \expandafters for #2
\expandafter\pgfutil@in@\expandafter#1\expandafter{#2}}
\def\qrr@tikz@taperedline@draw{%
\scope[transparency group,opacity=\qrr@tikz@taperedline@opacity]
\path[every tapered line] ([shift={(\pgf@tempa+90:{\qrr@tikz@taperedline@sourcewidth})}] \tikztostart)
-- ([shift={(\pgf@tempa+90:{\qrr@tikz@taperedline@targetwidth})}] \tikztotarget)
-- ([shift={(\pgf@tempa-90:{\qrr@tikz@taperedline@targetwidth})}] \tikztotarget)
-- ([shift={(\pgf@tempa-90:{\qrr@tikz@taperedline@sourcewidth})}] \tikztostart)
-- cycle;
\endscope}
\tikzset{
every tapered line/.style={draw=gray,fill=black!70},
tapered line/.code={\tikzset{/tikz/tapered line/.cd,#1}},% user interface
tapered line/.cd,
from/.code        = \@qrr@tikz@taperedline@to@false\@qrr@tikz@taperedline@from@true ,% internal
to/.code          = \@qrr@tikz@taperedline@to@true \@qrr@tikz@taperedline@from@false,% internal
from and to/.code = \@qrr@tikz@taperedline@to@true \@qrr@tikz@taperedline@from@true ,% internal
draw/.style={% internal
/tikz/to path={%
\pgfextra
\tikz@scan@one@point\pgfutil@firstofone(\tikztostart)\relax%  get coordinates for start
\pgf@xa\pgf@x\pgf@ya\pgf@y                                 %  and save them for later
\tikz@scan@one@point\pgfutil@firstofone(\tikztotarget)\relax% get coordinates for target
\csname pgfmathatan2\endcsname{\pgf@x}{\pgf@y}%               calculate angle
\let\pgf@tempa\pgfmathresult
\if@qrr@tikz@taperedline@to@% > and x
\qrr@tikz@taperedline@draw
\fi
\if@qrr@tikz@taperedline@from@% < and x
\let\pgfutil@tempb\qrr@tikz@taperedline@sourcewidth
\let\qrr@tikz@taperedline@sourcewidth\qrr@tikz@taperedline@targetwidth
\let\qrr@tikz@taperedline@targetwidth\pgfutil@tempb
\qrr@tikz@taperedline@draw
\fi
\endpgfextra
(\tikztotarget)
}
},
% user interface
source width/.store in = \qrr@tikz@taperedline@sourcewidth,
target width/.store in = \qrr@tikz@taperedline@targetwidth,
opacity/.store in      = \qrr@tikz@taperedline@opacity,
style/.style      = {/tikz/every tapered line/.append style={#1}},
>/.style          = {to,          draw},
</.style          = {from,        draw},
x/.style          = {from and to, draw},
% default values
source width=.7ex,
target width= 0pt,
opacity=.8,
}
\makeatother

\pgfdeclarelayer{bg}    % declare background layer
\pgfsetlayers{bg,main}  % set the order of the layers (main is the standard layer)

\begin{document}
\begin{tikzpicture}
\foreach \x/\y[count=\i from 0] in {0/0,1/2,.3/2.5,0/-2,-3/1,3/1.5} \node[draw=gray,fill=gray!50,shape=circle] (c-\i) at (\x,\y) {};
\begin{pgfonlayer}{bg}
\path (c-0)  edge[tapered line=<] (c-1)
edge[tapered line=>] (c-2)
edge[tapered line=x] (c-3);
\path (c-5)  edge[tapered line=>] (c-4);
\end{pgfonlayer}
\end{tikzpicture}
\begin{tikzpicture}
\path (0,0) edge [tapered line={<, opacity=1,target width=1ex, source width=2ex,
style={line join=round, decorate, decoration={random steps, segment length=1.6pt, amplitude=.4pt}}}]     (1,2);
\path (1,0) edge [tapered line={x, opacity=.5, target width=3ex, style={draw=orange,fill=red}}] (0,-1);
\end{tikzpicture}
\end{document}


## Output

-
How difficult would it be to update the triangle to a more general tapered structure? Like a trapezoid that has a wider base at one end and a narrower top. Something where you can specify (say) source width=<len> and target width=<len>, where a the option source width=0pt, target width=2ex would default to a triangle... I know nothing about tikz. – Werner Feb 28 '13 at 22:23
@Werner Pretty easy, take a look at the updated code. I haven’t updated the pictures, though. I wonder if it’d be better (Easier? “Nicer?”) to just use the trapezium shape, where you can use the usual node options and options extra for the trapezium shape (angles of the left and right sides). Possibilities … – Qrrbrbirlbel Feb 28 '13 at 22:52
This is amazing. Thank you, I'm also happy you addressed the coordinate vs node auto-detection. I had meant to bring up that point in the original post but forgot. – Tom Mar 3 '13 at 4:38