# How to draw a Sankey Diagram using TikZ

I would like to draw something similar to this (not exactly):

using TikZ. Ideally I would be able to set the width by variables and rejoin two flows into one again. Is there a good way to do this?

Since this will be my first TikZ diagram I am looking for pointers how to generally approach this problem, not for a complete solution, so that I can read the documentation effectively. How would I branch of a (variable) portion of a rectangle? How would I rejoin such portions into one again?

partial solution:

\documentclass{article}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}[scale=.5]
\draw[very thick] (1,0) -- (4,-1) -- (7,0) -- (7,-4) to [out=-90,in=180] (8,-5) -- (23,-5) -- (24,-6.5) --  (23,-8) -- (20,-8) to [out=180,in=0] (15,-10) -- (15,-10) (14,-10) -- (10,-10) to [out=0, in=90] (14,-14) -- (14,-8) to [out=90,in=0] (13,-7) -- (11,-7);
\draw[very thick] (15,-7) to [out=0,in=180] (20,-6) -- (11,-6) -- (13,-6) to [out=0,in=90] (15,-8) to (15,-20) -- (13.5,-21) -- (12,-20) to (12,-14) to [out=90,in=0] (11,-13) -- (5,-13) to [out=180,in=-90] (1,-9) to (1,0);
\draw[very thick] (4,-4) to [out=-90, in=180] (8,-8) -- (14,-8) (15,-8) -- (16,-8) to [out=0,in=180] (20,-7) to [out=180, in=0] (15,-9) (14,-9) -- (6,-9) to [out=180,in=-90] (4,-7) -- (4,-4);
\draw[very thick] (2,-6) to (2,-7) to [out=-90,in=180] (6,-11) -- (10,-11) to [out=0,in=90] (13,-14) -- (13,-14) to [out=90,in=0] (11,-12) -- (5,-12) to [out=180,in=-90] (2,-9) -- (2,-7);
\end{tikzpicture}
\end{document}


Gives:

but this is all hardcoded. I would ideally want to have the width of the flows variable (without needing to recalculate all the points by hand)

Syntax-wise I could imagine something like this:

\stream[width=x,direction=down] (initial_amount)
\redirect[initialamount,x\%][from=rightedge][to=right] (choide_A)
\redirect[choiceA,y\%][from=middle,rejoinrest=true][to=down] (choice_C)
\redirect[initial_amount,g\%][from=right][to=right] (choice_D) %i.e. that what's left of it after redirecting choice_A
\redirect[choid_D,h\%][from=left][to=down] (choice_E) %left in the sense that it the left side turned by 90 degrees

\join[initial_amount][choice_D][choice_C]
\join[choice_A][choice_E]


• streams have a flow direction, but tikz should be allowed to shift it (a right turn followed by a left turn or vice versa) for example when joining streams.
• layering should probably be so that first == lowest layer and whatever comes after should just pile on it
• straight portions, or shifting are adaptable in length/shift amount to whatever the spacing needs.

Update:

The following has a portion branching off to the right (\def'd by \choiceA)

\documentclass{article}
\usepackage{tikz}
\begin{document}
\def\startstreamx{1}
\def\startstreamy{1}
\def\initialwidth{6}
\def\initialheight{4}
\def\choiceA{.9}
\begin{tikzpicture}[scale=.5]
\draw[very thick] (\startstreamx,-\startstreamy-\initialheight) -- (\startstreamx,-\startstreamy) --    (\startstreamx+\initialwidth,-\startstreamy) -- (\startstreamx+\initialwidth,-\startstreamy-\initialheight);
\draw[very thick] (\startstreamx+\initialwidth,-\startstreamy-\initialheight) to [out=-90, in=180] (\startstreamx+\initialwidth+1,-\startstreamy-\initialheight-1);
\draw[very thick] (\startstreamx+\initialwidth-\choiceA*\initialwidth,-\startstreamy-\initialheight) to [out=-90, in=180] (\startstreamx+\initialwidth+1,-\startstreamy-\initialheight-1-\choiceA*\initialwidth);
\draw[very thick] (\startstreamx,-\startstreamy-\initialheight) to (\startstreamx,-\startstreamy-\initialheight-1-\choiceA*\initialwidth);
\draw[very thick] (\startstreamx+\initialwidth-\choiceA*\initialwidth,-\startstreamy-\initialheight) to (\startstreamx+\initialwidth-\choiceA*\initialwidth,-\startstreamy-\initialheight-1-\choiceA*\initialwidth);
\end{tikzpicture}
\end{document}


one could add now another such code snippet with the inital x and y and width values set to the end of the remaining (downwards) stream and again branch something off to the right.

-
In English it's called a 'flow diagram' ;), or more specifically a 'Sankey diagram' – Count Zero Jan 5 '12 at 17:45
Generally, we take questions more seriously when the questioner demonstrates that they have at least attempted to do what they are asking about. You'll probably get better answers if you show us your attempts at drawing the diagram yourself… – Seamus Jan 5 '12 at 18:39
That is an excellent first solution.. So, you mention that you don't like the hard coding. It would be good to see how you would like to be able to specify this. An example syntax would be useful. – Peter Grill Jan 5 '12 at 22:20
I think this calls for a new package... :) – Count Zero Jan 6 '12 at 19:44
Just noting that matplotlib (plotting library for Python) has a module for creating Sankey diagrams, and with matplotlib2tikz you could get pgfplots/TikZ code. The code won't be necessarily elegant, and it will need some tweeks, but I figured it was worth mentioning. If you feel it can be useful, I can add an answer elaborating a little. – Torbjørn T. Sep 2 '12 at 15:31

# Edit

Since release 3.0 of TikZ/PGF, the arguments of atan2 are swapped.

# New Version (TikZ/PGF 3.0)

Here is a first attempt with my new environment sankeydiagram.

Its optional argument is useful to fix some global parameters:

• sankey tot quantity is a number and represents the total quantity of the global flow (default value: 100 for 100%).

• sankey tot length is the width of the global flow (default value: 100pt).

• sankey min radius is the minimum radius of each turn (default value: 30pt).

• sankey fill is the style used to fill the flows.

• sankey draw is the style used to draw the flows.

• sankey debug (a flag) is useful to debug a diagram during its construction (default value: false).

The sankeydiagram environment defines some useful commands to construct a sankey diagram:

• \sankeynode{prop}{angle}{name}{pos} makes a sankey node (a flow) of prop capacity (in quantity units), named name. Its orientation and position are given by angle and pos.

• \sankeynodestart and \sankeynodeend are similar to \sankeynode (same arguments) but make respectively and flow starting and a flow ending.

• \sankeyadvance{name}{distance} moves forward the sankey node named name.

• \sankeyturn{name}{angle} turns the sankey node named name.

• \sankeyfork{name}{list of forks} forks the sankey node named name. The list of forks is a list of pairs: quantity/name (the sum of quantities must be equal to the quantity of sankey node to fork).

Here is the results (first without sankey debug then with sankey debug).

And, now, the code:

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}
\usepackage{etoolbox}

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

\newif\ifsankeydebug

\newenvironment{sankeydiagram}[1][]{

\def\sankeyflow##1##2{% sn, en
\path[sankey fill]
let
\p1=(##1.north east),\p2=(##1.south east),
\n1={atan2(\y1-\y2,\x1-\x2)-90},
\p3=(##2.north west),\p4=(##2.south west),
\n2={atan2(\y3-\y4,\x3-\x4)+90}
in
(\p1) to[out=\n1,in=\n2] (\p3) --
(\p4) to[in=\n1,out=\n2] (\p2) -- cycle;
\draw[sankey draw]
let
\p1=(##1.north east),\p2=(##1.south east),
\n1={atan2(\y1-\y2,\x1-\x2)-90},
\p3=(##2.north west),\p4=(##2.south west),
\n2={atan2(\y3-\y4,\x3-\x4)+90}
in
(\p1) to[out=\n1,in=\n2] (\p3)
(\p4) to[in=\n1,out=\n2] (\p2);
}

\tikzset{
sankey tot length/.store in=\sankeytotallen,
sankey tot quantity/.store in=\sankeytotalqty,
sankey arrow length/.store in=\sankeyarrowlen,
sankey debug/.is if=sankeydebug,
sankey debug=false,
sankey flow/.style={
to path={
\pgfextra{
\pgfinterruptpath
\edef\sankeystart{\tikztostart}
\edef\sankeytarget{\tikztotarget}
\sankeyflow{\sankeystart}{\sankeytarget}
\endpgfinterruptpath
}
},
},
sankey node/.style={
inner sep=0,minimum height={sankeyqtytolen(##1)},
minimum width=0,draw=none,line width=0pt,
},
% sankey angle
sankey angle/.store in=\sankeyangle,
% sankey default styles
sankey fill/.style={line width=0pt,fill,white},
sankey draw/.style={draw=black,line width=.4pt},
}

\newcommand\sankeynode[4]{%prop,orientation,name,pos
\node[sankey node=##1,rotate=##2] (##3) at (##4) {};
\ifsankeydebug
\begin{pgfonlayer}{sankeydebug}
\draw[red,|-|] (##3.north west) -- (##3.south west);
\pgfmathsetmacro{\len}{sankeyqtytolen(##1)/3}
\draw[red] (##3.west)
-- ($(##3.west)!\len pt!90:(##3.south west)$)
node[font=\tiny,text=black] {##3};
\end{pgfonlayer}
\fi
}

\newcommand\sankeynodestart[4]{%prop,orientation,name,pos
\sankeynode{##1}{##2}{##3}{##4}
\begin{scope}[shift={(##3)},rotate=##2]
\path[sankey fill]
(##3.north west) -- ++(-\sankeyarrowlen,0)
-- ([xshift=-\sankeyarrowlen/6]##3.west)
-- ([xshift=-\sankeyarrowlen]##3.south west)
-- (##3.south west) -- cycle;
\path[sankey draw]
(##3.north west) -- ++(-\sankeyarrowlen,0)
-- ([xshift=-\sankeyarrowlen/6]##3.west)
-- ([xshift=-\sankeyarrowlen]##3.south west)
-- (##3.south west);
\end{scope}
}

\newcommand\sankeynodeend[4]{%prop,orientation,name,pos
\sankeynode{##1}{##2}{##3}{##4}
\begin{scope}[shift={(##3)},rotate=##2]
\path[sankey fill]
(##3.north east)
-- ([xshift=\sankeyarrowlen]##3.east)
-- (##3.south west) -- cycle;
\path[sankey draw]
(##3.north east)
-- ([xshift=\sankeyarrowlen]##3.east)
-- (##3.south west);
\end{scope}
}

\edef\name{##2}
\ifstrempty{##1}{
\def\newname{##2}
\edef\name{##2-old}
\path [late options={name=##2,alias=\name}];
}{
\def\newname{##1}
}
\path
let
% sankey node angle
\p1=(##2.north east),
\p2=(##2.south east),
\n1={atan2(\y1-\y2,\x1-\x2)-90},
% sankey prop
\p3=($(\p1)-(\p2)$),
\n2={sankeylentoqty(veclen(\x3,\y3))},
% next position
\p4=($(##2.east)!##3!-90:(##2.north east)$)
in
\pgfextra{
\pgfmathsetmacro{\prop}{\n2}
\pgfinterruptpath
\sankeynode{\prop}{\n1}{\newname}{\p4}
\path (\name) to[sankey flow] (\newname);
\endpgfinterruptpath
};
}

\newcommand\sankeyturn[3][]{%newname,name,angle
\edef\name{##2}
\ifstrempty{##1}{
\def\newname{##2}
\edef\name{##2-old}
\path [late options={name=##2,alias=\name}];
}{
\def\newname{##1}
}
\ifnumgreater{##3}{0}{
\typeout{turn acw: ##3}
\path
let
% sankey node angle
\p1=(##2.north east),
\p2=(##2.south east),
\p3=($(\p1)!-\sankeyminradius!(\p2)$),
\n1={atan2(\y1-\y2,\x1-\x2)-90},
% sankey prop
\p4=($(\p1)-(\p2)$),
\n2={sankeylentoqty(veclen(\x4,\y4))},
\p5=(##2.east),
\p6=($(\p3)!1!##3:(\p5)$)
in
\pgfextra{
\pgfmathsetmacro{\prop}{\n2}
\pgfinterruptpath
% \fill[red] (\p3) circle (2pt);
% \fill[blue](\p6) circle (2pt);
\sankeynode{\prop}{\n1+##3}{\newname}{\p6}
\path (\name) to[sankey flow] (\newname);
\endpgfinterruptpath
};
}{
\typeout{turn acw: ##3}
\path
let
% sankey node angle
\p1=(##2.south east),
\p2=(##2.north east),
\p3=($(\p1)!-\sankeyminradius!(\p2)$),
\n1={atan2(\y1-\y2,\x1-\x2)+90},
% sankey prop
\p4=($(\p1)-(\p2)$),
\n2={sankeylentoqty(veclen(\x4,\y4))},
\p5=(##2.east),
\p6=($(\p3)!1!##3:(\p5)$)
in
\pgfextra{
\pgfmathsetmacro{\prop}{\n2}
\pgfinterruptpath
% \fill[red] (\p3) circle (2pt);
% \fill[blue](\p6) circle (2pt);
\sankeynode{\prop}{\n1+##3}{\newname}{\p6}
\path (\name) to[sankey flow] (\newname);
\endpgfinterruptpath
};
}
}

\newcommand\sankeyfork[2]{%name,list of forks
\def\name{##1}
\def\listofforks{##2}
\xdef\sankeytot{0}
\path
let
% sankey node angle
\p1=(\name.north east),
\p2=(\name.south east),
\n1={atan2(\y1-\y2,\x1-\x2)-90},
% sankey prop
\p4=($(\p1)-(\p2)$),
\n2={sankeylentoqty(veclen(\x4,\y4))}
in
\pgfextra{
\pgfmathsetmacro{\iprop}{\n2}
}
\foreach \prop/\name[count=\c] in \listofforks {
let
\p{start \name}=($(\p1)!\sankeytot/\iprop!(\p2)$),
\n{nexttot}={\sankeytot+\prop},
\p{end \name}=($(\p1)!\n{nexttot}/\iprop!(\p2)$),
\p{mid \name}=($(\p{start \name})!.5!(\p{end \name})$)
in
\pgfextra{
\xdef\sankeytot{\n{nexttot}}
\pgfinterruptpath
\sankeynode{\prop}{\n1}{\name}{\p{mid \name}}
\endpgfinterruptpath
}
}
\pgfextra{
\pgfmathsetmacro{\diff}{abs(\iprop-\sankeytot)}
\pgfmathtruncatemacro{\finish}{\diff<0.01?1:0}
\ifnumequal{\finish}{1}{}{
\message{*** Warning: bad sankey fork (maybe)...}
\message{\iprop-\sankeytot}
}
};
}

\tikzset{
% default values,
declare function={
sankeyqtytolen(\qty)=\qty/\sankeytotalqty*\sankeytotallen;
sankeylentoqty(\len)=\len/\sankeytotallen*\sankeytotalqty;
},
sankey tot length=100pt,
sankey tot quantity=100,
sankey arrow length=10pt,%
% user values
#1}
}{
}

\begin{document}
\begin{tikzpicture}[x=1pt,y=1pt]

\begin{sankeydiagram}[
sankey tot length=90pt,%
sankey tot quantity=6,%
sankey fill/.style={
draw,line width=0pt,
fill,
lime!50,
},
sankey draw/.style={
draw=black,
line width=1pt,
line cap=round,
line join=round,
},
sankey debug,
]
\sankeynodestart{6}{-90}{p0}{0,100};

\sankeyfork{p0}{3/p1,3/p2}

\sankeyturn{p1}{90}

\sankeyfork{p2}{2/p3,1/p4}

\sankeyturn{p3}{90}

\sankeyfork{p3}{1/p5,1/p6}

\sankeyfork{p1}{1/p7,1/p8,1/p9}

\sankeyturn{p4}{90}

\sankeynode{3}{0}{p11}{[shift={(50pt,-15pt)}]p7}
\sankeyfork{p11}{1/p7a,1/p9a,1/p5a}
\path (p7) to[sankey flow] (p7a);
\path (p9) to[sankey flow] (p9a);
\path (p5) to[sankey flow] (p5a);
\sankeynodeend{3}{0}{p11}{p11}

{
\tikzset{
sankey fill/.append style={
line width=0pt,
lime!50!green!50,
}
}
\sankeyturn{p8}{-90}

\sankeyturn{p6}{-90}
\sankeyturn{p4}{-90}

\sankeynode{3}{-90}{p10}{[shift={(-15pt,-60pt)}]p8}
\sankeyfork{p10}{1/p8a,1/p6a,1/p4a}
\path (p4) to[sankey flow] (p4a);
\path (p6) to[sankey flow] (p6a);
\path (p8) to[sankey flow] (p8a);
\sankeynodeend{3}{-90}{p10}{p10}
}

\end{sankeydiagram}
\end{tikzpicture}
\end{document}


Here is another example with the same preamble (it's the same sankey diagram as your first example but with adjusted value: Industrie=86.1 . Without adjustment, there are bad sums...) :

Then the code (without preamble):

\usetikzlibrary{positioning}
\begin{document}
\begin{tikzpicture}

\begin{sankeydiagram}[
sankey tot length=5cm,%
sankey tot quantity=524.3,%
sankey fill/.style={
draw,line width=0pt,
fill,
cyan!50!blue!50!black,
},
sankey draw/.style={
draw=none,
line width=1pt,
line cap=round,
line join=round,
},
%sankey debug,
]

\sankeynodestart{7.2}{-90}{B}{-.5,0}
\coordinate[below=1mm of B.center] (B label);
\sankeynodestart{137.3}{-90}{GI}{1,0}
\coordinate[below=1mm of GI.center] (GI label);
\sankeynodestart{397.8}{-90}{I}{4,0}
\coordinate[below=1mm of I.center] (I label);

\sankeynode{542.3}{-90}{EI}{2.86,-1}
\sankeyfork{EI}{397.8/Ia,137.3/GIa,7.2/Ba}
\path (I) to[sankey flow] (Ia);
\path (GI) to[sankey flow] (GIa);
\path (B) to[sankey flow] (Ba);
\coordinate (EI label) at (EI);

\sankeyfork{EI}{63.1/EB,479.2/P}

\sankeyturn{EB}{90}
\coordinate (EB label) at (EB.center);
\sankeynodeend{63.1}{0}{EB}{EB}

\coordinate (P label) at (P);

\sankeyfork{P}{33.5/NV,445.7/P}

{
\tikzset{sankey fill/.append style={cyan!80!lime!50!gray}}
\sankeyturn{NV}{90}
\coordinate (NV label) at (NV);
\sankeynodeend{33.5}{0}{NV}{NV}
}

\sankeyfork{P}{118.1/U,327.6/P}

{
\tikzset{sankey fill/.append style={orange!70!gray!50}}
\sankeyturn{U}{90}
\coordinate (U label) at (U);
\sankeynodeend{118.1}{0}{U}{U}
}

\sankeyfork{P}{327.2/P,0.4/SD}

{
\sankeyturn{SD}{-90}
\coordinate (SD label) at (SD);
\sankeynodeend{0.4}{0}{SD}{SD}
}

\sankeyfork{P}{18.8/VE,308.4/E}

{
\tikzset{sankey fill/.append style={orange!70!gray!30}}
\sankeyturn{VE}{90}
\coordinate (VE label) at (VE);
\sankeynodeend{18.8}{0}{VE}{VE}
}

\coordinate (E label) at (E);

\sankeyfork{E}{135.1/H+GHD,87.2/V,86.1/In}

\sankeyturn{In}{-90}
\sankeyturn{In}{90}
\coordinate (In label)  at (In);
\sankeynodeend{86.7}{-90}{In}{In}

\coordinate (V label) at (V);
\sankeynodeend{87.2}{-90}{V}{V}

\sankeyturn{H+GHD}{90}
\sankeyfork{H+GHD}{47.0/GHD,88.1/H}

\sankeyturn{H}{-90}
\coordinate (H label) at (H);
\sankeynodeend{88.1}{-90}{H}{H}

\sankeyturn{GHD}{-90}
\coordinate (GHD label) at (GHD);
\sankeynodeend{47}{-90}{GHD}{GHD}

% labels
\tikzset{
label/.style={
fill=white,inner sep=.5mm,text=cyan!50!blue!50!black,
font=\sffamily\bfseries\small,inner sep=1mm,
align=center,
},
}
\node[label,anchor=north] (B label) at (B label) {7.1};
\node[label,left=1mm of B label] {Bestands-\\entnahme};
\node[label,anchor=north] at (GI label) {137.3};
\node[label,above=5mm of GI label] {Gewinnung\\im Inland};
\node[label,anchor=north] at (I label) {397.8};
\node[label,above=5mm of I label] {Import};

\node[label] at (EI label) {542.3\\Energieaufkommen im Inland};

\node[label,anchor=center] (EB label) at (EB label) {63.1};
\node[label,above=1mm of EB label] {Export und\\Bunkerung};

\node[label] at (P label) {479.1\\Primärenergieverbrauch};

\node[label,anchor=center] (NV label) at (NV label) {33.5};
\node[label,above=0mm of NV label] {Nichtenergetischer Verbrauch};

\node[label,anchor=center] (U label) at (U label) {118.1};
\node[label,below=4mm of U label] {Umwandlungsverluste};

\node[label,anchor=center] (SD label) at (SD label) {0.4};
\node[label,above=0mm of SD label] {Statistische\\Differenzen};

\node[label,anchor=center] (VE label) at (VE label) {18.8};
\node[label,below=0mm of VE label] {Verbrauch in den\\Energiesktoren};

\node[label,anchor=north] (E label) at (E label) {308.4\\Endenergieverbrauch};

\node[label,anchor=north] (In label) at (In label) {86.1};
\node[label,anchor=north,below=1cm of In label] {Industrie};

\node[label,anchor=north] (V label) at (V label) {87.2};
\node[label,anchor=north,below=1cm of V label] {Verkehr};

\node[label,anchor=north] (H label) at (H label) {87.2};
\node[label,anchor=north,below=1cm of H label] {Haushalte};

\node[label,anchor=north] (GHD label) at (GHD label) {47.0};
\node[label,anchor=north,below=1cm of GHD label] {Gewerbe, Handel\\Diensleistungen};
\end{sankeydiagram}
\end{tikzpicture}
\end{document}

-
Hoooolyyyy... This is awesome! – morbusg Jan 1 '13 at 16:19
@morbusg Nope. This is Paul Gaborit:-) – Marc van Dongen Jan 1 '13 at 21:07
wow, this is really impressive. – luksen Jan 1 '13 at 23:35
This should be some kind of package. – You Jan 2 '13 at 1:47
@Paul would it be feasible to compute the spacings (i.e how much to advance without crashing into other parts of the diagram) automatically? – luksen Jan 2 '13 at 15:22

A great alternative to using tikz directly is Matplotlib and the Sankey module. You can see example codes here: Matplotlib Sankey example

I think it is a very much better way in creating a sankey diagram.

With XeLaTeX and matplotlib 1.2 you can use matplotlib-backend-pgf

A short code example using the sankey example diagram and creating a pdf diagram:

import matplotlib as mpl
mpl.use("module://backend_pgf")
import matplotlib.pyplot as plt
from matplotlib.sankey import Sankey

Sankey(flows=[0.25, 0.15, 0.60, -0.20, -0.15, -0.05, -0.50, -0.10],
labels=['', '', '', 'First', 'Second', 'Third', 'Fourth', 'Fifth'],
orientations=[-1, 1, 0, 1, 1, 1, 0, -1]).finish()

# remove the frame in the sankey diagram:
a = plt.gca()
a.set_frame_on(False)

# Save it to pdf:
plt.savefig("sankey.pdf", bbox_inches='tight')


In your tex file you can then include the pdf image:

% tex file:
\includegraphics[width=0.7\textwidth]{img/sankey.pdf}

-
He also mentioned the detail given in your answer no? – percusse Dec 20 '12 at 13:22
A short example of how this works, with both Python and LaTeX code, would be a nice addition. (<nitpickmode>Also, matplotlib2tikz isn't really part of matplotlib IIUC, it is a separate project.</nitpickmode>) Edit: Yes, I know there are examples on the Matplotlib website, but it would make the answer more self-contained. – Torbjørn T. Dec 20 '12 at 14:11
Note: I'm not notified of your comment unless you write @TorbjørnT. I would have added an answer had someone asked me to, I didn't at first because I was unsure whether it was of interest to the OP. But never mind. I don't think two answers about this is necessary, but I may expand your answer later, if you haven't already. – Torbjørn T. Dec 20 '12 at 14:40
@TorbjørnT. I added a example based on matplotlib-backend-pgf which I am using right now. It is included in matplotlib 1.2 – Thomas Dec 29 '12 at 12:11

I think that the usage of double distance is the key of this type of drawings. Here it is a little example

\documentclass{standalone}

\usepackage{tikz}

\begin{document}
\begin{tikzpicture}
\draw[help lines] (-2,-4)grid(9,5);
\def\lI{4cm}
\def\rIfrazI{.4}
\pgfmathsetmacro\rIfrazII{1-\rIfrazI}

\def\rl{2}
\draw[line width      = 2pt,
double distance = \lI-\pgflinewidth] (0,0)--(\rl,0);

%%%%%%%%%%%%
% BRANCH I %
%%%%%%%%%%%%
\def\rIl{5}
\draw[line width      = 2pt,
double distance = \rIfrazI*\lI-\pgflinewidth] (\rl,{(1/2-\rIfrazI/2)*\lI})--++(\rIl,0);

% ... you continue

%%%%%%%%%%%%%
% BRANCH II %
%%%%%%%%%%%%%
\def\rIIl{2.7}
\draw[line width      = 2pt,
double distance = \rIfrazII*\lI-\pgflinewidth]
(\rl,{(-1/2+\rIfrazII/2)*\lI})--++
start angle = 90,
end angle   = 0]
coordinate(r);

% ... you continue
\end{tikzpicture}
\end{document}


Obviously, to develop and improve.

-
this indeed looks helpful – luksen Jan 7 '12 at 15:59

Just to let you know: the atan2 function was changed with pgf/tikz 3.0. You need to switch x and y argument for every atan2 call. Saying this, the working code is:

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

\newif\ifsankeydebug

\newenvironment{sankeydiagram}[1][]{

\def\sankeyflow##1##2{% sn, en
\path[sankey fill]
let
\p1=(##1.north east),\p2=(##1.south east),
\n1={atan2(\y1-\y2,\x1-\x2)-90},
\p3=(##2.north west),\p4=(##2.south west),
\n2={atan2(\y3-\y4,\x3-\x4)+90}
in
(\p1) to[out=\n1,in=\n2] (\p3) --
(\p4) to[in=\n1,out=\n2] (\p2) -- cycle;
\draw[sankey draw]
let
\p1=(##1.north east),\p2=(##1.south east),
\n1={atan2(\y1-\y2,\x1-\x2)-90},
\p3=(##2.north west),\p4=(##2.south west),
\n2={atan2(\y3-\y4,\x3-\x4)+90}
in
(\p1) to[out=\n1,in=\n2] (\p3)
(\p4) to[in=\n1,out=\n2] (\p2);
}

\tikzset{
sankey tot length/.store in=\sankeytotallen,
sankey tot quantity/.store in=\sankeytotalqty,
sankey arrow length/.store in=\sankeyarrowlen,
sankey debug/.is if=sankeydebug,
sankey debug=false,
sankey flow/.style={
to path={
\pgfextra{
\pgfinterruptpath
\edef\sankeystart{\tikztostart}
\edef\sankeytarget{\tikztotarget}
\sankeyflow{\sankeystart}{\sankeytarget}
\endpgfinterruptpath
}
},
},
sankey node/.style={
inner sep=0,minimum height={sankeyqtytolen(##1)},
minimum width=0,draw=none,line width=0pt,
},
% sankey angle
sankey angle/.store in=\sankeyangle,
% sankey default styles
sankey fill/.style={line width=0pt,fill,white},
sankey draw/.style={draw=black,line width=.4pt},
}

\newcommand\sankeynode[4]{%prop,orientation,name,pos
\node[sankey node=##1,rotate=##2] (##3) at (##4) {};
\ifsankeydebug
\begin{pgfonlayer}{sankeydebug}
\draw[red,|-|] (##3.north west) -- (##3.south west);
\pgfmathsetmacro{\len}{sankeyqtytolen(##1)/3}
\draw[red] (##3.west)
-- ($(##3.west)!\len pt!90:(##3.south west)$)
node[font=\tiny,text=black] {##3};
\end{pgfonlayer}
\fi
}

\newcommand\sankeynodestart[4]{%prop,orientation,name,pos
\sankeynode{##1}{##2}{##3}{##4}
\begin{scope}[shift={(##3)},rotate=##2]
\path[sankey fill]
(##3.north west) -- ++(-\sankeyarrowlen,0)
-- ([xshift=-\sankeyarrowlen/6]##3.west)
-- ([xshift=-\sankeyarrowlen]##3.south west)
-- (##3.south west) -- cycle;
\path[sankey draw]
(##3.north west) -- ++(-\sankeyarrowlen,0)
-- ([xshift=-\sankeyarrowlen/6]##3.west)
-- ([xshift=-\sankeyarrowlen]##3.south west)
-- (##3.south west);
\end{scope}
}

\newcommand\sankeynodeend[4]{%prop,orientation,name,pos
\sankeynode{##1}{##2}{##3}{##4}
\begin{scope}[shift={(##3)},rotate=##2]
\path[sankey fill]
(##3.north east)
-- ([xshift=\sankeyarrowlen]##3.east)
-- (##3.south west) -- cycle;
\path[sankey draw]
(##3.north east)
-- ([xshift=\sankeyarrowlen]##3.east)
-- (##3.south west);
\end{scope}
}

\edef\name{##2}
\ifstrempty{##1}{
\def\newname{##2}
\edef\name{##2-old}
\path [late options={name=##2,alias=\name}];
}{
\def\newname{##1}
}
\path
let
% sankey node angle
\p1=(##2.north east),
\p2=(##2.south east),
\n1={atan2(\y1-\y2,\x1-\x2)-90},
% sankey prop
\p3=($(\p1)-(\p2)$),
\n2={sankeylentoqty(veclen(\x3,\y3))},
% next position
\p4=($(##2.east)!##3!-90:(##2.north east)$)
in
\pgfextra{
\pgfmathsetmacro{\prop}{\n2}
\pgfinterruptpath
\sankeynode{\prop}{\n1}{\newname}{\p4}
\path (\name) to[sankey flow] (\newname);
\endpgfinterruptpath
};
}

\newcommand\sankeyturn[3][]{%newname,name,angle
\edef\name{##2}
\ifstrempty{##1}{
\def\newname{##2}
\edef\name{##2-old}
\path [late options={name=##2,alias=\name}];
}{
\def\newname{##1}
}
\ifnumgreater{##3}{0}{
\typeout{turn acw: ##3}
\path
let
% sankey node angle
\p1=(##2.north east),
\p2=(##2.south east),
\p3=($(\p1)!-\sankeyminradius!(\p2)$),
\n1={atan2(\y1-\y2,\x1-\x2)-90},
% sankey prop
\p4=($(\p1)-(\p2)$),
\n2={sankeylentoqty(veclen(\x4,\y4))},
\p5=(##2.east),
\p6=($(\p3)!1!##3:(\p5)$)
in
\pgfextra{
\pgfmathsetmacro{\prop}{\n2}
\pgfinterruptpath
% \fill[red] (\p3) circle (2pt);
% \fill[blue](\p6) circle (2pt);
\sankeynode{\prop}{\n1+##3}{\newname}{\p6}
\path (\name) to[sankey flow] (\newname);
\endpgfinterruptpath
};
}{
\typeout{turn acw: ##3}
\path
let
% sankey node angle
\p1=(##2.south east),
\p2=(##2.north east),
\p3=($(\p1)!-\sankeyminradius!(\p2)$),
\n1={atan2(\y1-\y2,\x1-\x2)+90},
% sankey prop
\p4=($(\p1)-(\p2)$),
\n2={sankeylentoqty(veclen(\x4,\y4))},
\p5=(##2.east),
\p6=($(\p3)!1!##3:(\p5)$)
in
\pgfextra{
\pgfmathsetmacro{\prop}{\n2}
\pgfinterruptpath
% \fill[red] (\p3) circle (2pt);
% \fill[blue](\p6) circle (2pt);
\sankeynode{\prop}{\n1+##3}{\newname}{\p6}
\path (\name) to[sankey flow] (\newname);
\endpgfinterruptpath
};
}
}

\newcommand\sankeyfork[2]{%name,list of forks
\def\name{##1}
\def\listofforks{##2}
\xdef\sankeytot{0}
\path
let
% sankey node angle
\p1=(\name.north east),
\p2=(\name.south east),
\n1={atan2(\y1-\y2,\x1-\x2)-90},
% sankey prop
\p4=($(\p1)-(\p2)$),
\n2={sankeylentoqty(veclen(\x4,\y4))}
in
\pgfextra{
\pgfmathsetmacro{\iprop}{\n2}
}
\foreach \prop/\name[count=\c] in \listofforks {
let
\p{start \name}=($(\p1)!\sankeytot/\iprop!(\p2)$),
\n{nexttot}={\sankeytot+\prop},
\p{end \name}=($(\p1)!\n{nexttot}/\iprop!(\p2)$),
\p{mid \name}=($(\p{start \name})!.5!(\p{end \name})$)
in
\pgfextra{
\xdef\sankeytot{\n{nexttot}}
\pgfinterruptpath
\sankeynode{\prop}{\n1}{\name}{\p{mid \name}}
\endpgfinterruptpath
}
}
\pgfextra{
\pgfmathsetmacro{\diff}{abs(\iprop-\sankeytot)}
\pgfmathtruncatemacro{\finish}{\diff<0.01?1:0}
\ifnumequal{\finish}{1}{}{
\message{*** Warning: bad sankey fork (maybe)...}
\message{\iprop-\sankeytot}
}
};
}

\tikzset{
% default values,
declare function={
sankeyqtytolen(\qty)=\qty/\sankeytotalqty*\sankeytotallen;
sankeylentoqty(\len)=\len/\sankeytotallen*\sankeytotalqty;
},
sankey tot length=100pt,
sankey tot quantity=100,