145

This question led to a new package:
sankey

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:

enter image description here

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]

Points I'm thinking about

  • 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.

enter image description here

Paul Gaborit
  • 70,770
  • 10
  • 176
  • 283
luksen
  • 1,553
  • 5
    In English it's called a 'flow diagram' ;), or more specifically a 'Sankey diagram' – Count Zero Jan 05 '12 at 17:45
  • good to know.. edited the title – luksen Jan 05 '12 at 17:49
  • Welcome to TeX.sx! Usually, we don't put a greeting or a "thank you" in our posts. While this might seem strange at first, it is not a sign of lack of politeness, but rather part of our trying to keep everything very concise. Upvoting is the preferred way here to say "thank you" to users who helped you. – doncherry Jan 05 '12 at 18:10
  • 4
    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 05 '12 at 18:39
  • @Seamus yeah that's understandable. Even though I've been texing for quiet some time, it'd be my first TikZ image. Before wandering off into the documentation of it all, i thought it'd be good to have some pointers what to look for expecially. – luksen Jan 05 '12 at 19:00
  • @MarcoDaniel no not exactly this one. the one I want to draw is a little more complex (rejoining flows, etc. i can scan a drawing if you wish) but I was rather asking how to generally approach such a diagram. From there I would try it on my own, and maybe come back if I get stuck. – luksen Jan 05 '12 at 19:01
  • @luksen So if you got a fully working solution, you wouldn't understand it? What's the point of that? – Seamus Jan 05 '12 at 19:25
  • @Seamus I am not asking or a fully working example (much less even for a tikz reproduction of the above image). I was just asking for general pointers how to attack this problem (maybe a code snippet of a flow from which a certain portion exits to the right). I'll look into the examples and see what i can do, though. – luksen Jan 05 '12 at 19:31
  • @luksen Perhap you should rephrase your question to make clear that what you're asking for is general tips? At the moment it does read a little bit like you are just asking someone to do the work for you. – Seamus Jan 05 '12 at 19:45
  • @Seamus done hope it's better now. – luksen Jan 05 '12 at 19:57
  • This may be of use: http://tex.stackexchange.com/questions/15779/materials-for-learning-tikz – Seamus Jan 05 '12 at 21:02
  • thanks I updated my answer with my first (albeit hardcoded) try. – luksen Jan 05 '12 at 22:03
  • 3
    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 05 '12 at 22:20
  • well the input i have is the width of each of the flows. Ideally I want to provide a array of these numbers (probably the widths 'leaf' flows would be enough sine the parent flows just add up). The general outline should be as given (although i admit the spacing could be nicer, esp. the width of the flows shouldn't change all that much during bending and such.) – luksen Jan 05 '12 at 22:25
  • 1
    i updated my example with better spacing. i you look at it it's reall onyl 5 'leaf' streams that define the entire picture. – luksen Jan 05 '12 at 23:02
  • Instead of describing the syntax, could you actually provide the example syntax? For instance, how would you prefer to specify exactly what you have done in the given example? You should edit your question to show this. – Peter Grill Jan 05 '12 at 23:29
  • 1
    @PeterGrill i added some pseudo code, of what I could imagine. I have no idea if this is anywhere close to real syntax, i'm just reading through some docs. – luksen Jan 06 '12 at 00:27
  • 5
    I think this calls for a new package... :) – Count Zero Jan 06 '12 at 19:44
  • @luksen Also, you have managed to answer your question pretty good and thoroughly as far as I can see :) – percusse Jan 06 '12 at 22:55
  • 2
    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 02 '12 at 15:31

4 Answers4

134

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).

enter image description here

enter image description here

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 min radius/.store in=\sankeyminradius,
    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}
  }

  \newcommand\sankeyadvance[3][]{%newname,name,distance
    \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 min radius=30pt,%
    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 min radius=15pt,%
    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};
    \sankeyadvance{p0}{50pt}

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

    \sankeyturn{p1}{90}
    \sankeyadvance{p1}{20pt}

    \sankeyadvance{p2}{60pt}

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

    \sankeyturn{p3}{90}
    \sankeyadvance{p3}{50pt}

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

    \sankeyadvance{p5}{70pt}

    \sankeyfork{p1}{1/p7,1/p8,1/p9}
    \sankeyadvance{p7}{50pt}
    \sankeyadvance{p9}{50pt}

    \sankeyadvance{p4}{40pt}
    \sankeyturn{p4}{90}
    \sankeyadvance{p4}{65pt}

    \sankeyadvance{p7}{40pt}

    \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);
    \sankeyadvance{p11}{30pt}
    \sankeynodeend{3}{0}{p11}{p11}

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

      \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);
      \sankeyadvance{p10}{30pt}
      \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...) :

enter image description here

Then the code (without preamble):

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

  \begin{sankeydiagram}[
    sankey tot length=5cm,%
    sankey tot quantity=524.3,%
    sankey min radius=3mm,%
    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);
    \sankeyadvance{B}{5mm}
    \sankeynodestart{137.3}{-90}{GI}{1,0}
    \coordinate[below=1mm of GI.center] (GI label);
    \sankeyadvance{GI}{5mm}
    \sankeynodestart{397.8}{-90}{I}{4,0}
    \coordinate[below=1mm of I.center] (I label);
    \sankeyadvance{I}{5mm}

    \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);
    \sankeyadvance{EI}{5mm}
    \coordinate (EI label) at (EI);
    \sankeyadvance{EI}{5mm}

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

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

    \sankeyadvance{P}{10mm}
    \coordinate (P label) at (P);
    \sankeyadvance{P}{5mm}

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

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

    \sankeyadvance{P}{10mm}

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

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

    \sankeyadvance{P}{10mm}

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

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

    \sankeyadvance{P}{8mm}

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

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

    \sankeyadvance{E}{8mm}
    \coordinate (E label) at (E);
    \sankeyadvance{E}{20mm}

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

    \sankeyturn{In}{-90}
    \sankeyadvance{In}{10mm}
    \sankeyturn{In}{90}
    \sankeyadvance{In}{5mm}
    \coordinate (In label)  at (In);
    \sankeyadvance{In}{10mm}
    \sankeynodeend{86.7}{-90}{In}{In}

    \sankeyadvance{V}{19mm}
    \coordinate (V label) at (V);
    \sankeyadvance{V}{10mm}
    \sankeynodeend{87.2}{-90}{V}{V}

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

    \sankeyturn{H}{-90}
    \sankeyadvance{H}{.5mm}
    \coordinate (H label) at (H);
    \sankeyadvance{H}{10mm}
    \sankeynodeend{88.1}{-90}{H}{H}

    \sankeyadvance{GHD}{30mm}
    \sankeyturn{GHD}{-90}
    \sankeyadvance{GHD}{8.5mm}
    \coordinate (GHD label) at (GHD);
    \sankeyadvance{GHD}{10mm}
    \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}
Paul Gaborit
  • 70,770
  • 10
  • 176
  • 283
15

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}
Thomas
  • 931
  • 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. (Also, matplotlib2tikz isn't really part of matplotlib IIUC, it is a separate project.) 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
  • I think I had to make a new answer because your comment is the last comment and I overlooked it before. I found the matplotlib example myself. This answer is a better place to find for somebody. Feel free to add your content to my answer or add another (more useful) answer. BTW I upvoted your comment to appear in the non-hidden comments. – Thomas Dec 20 '12 at 14:33
  • 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
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})--++
                   (\rIIl,0)arc[radius      = {(\rIfrazII*\lI-\pgflinewidth)/2+1cm},
                                start angle = 90,
                                end angle   = 0]
                            coordinate(r);

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

Obviously, to develop and improve.

Azoun
  • 2,317
9

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 min radius/.store in=\sankeyminradius,
    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}
  }

  \newcommand\sankeyadvance[3][]{%newname,name,distance
    \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 min radius=30pt,%
    sankey arrow length=10pt,%
    % user values
    #1}
}{
}
xamf
  • 151
  • 1
  • 4
  • 3
    Welcome to TeX.SX! This should probably be a comment on the answers using this function rather than a separate answer. – Paul Gessler Apr 25 '14 at 16:01
  • 3
    Actually I wanted to do this, but since my reputation is not high enough I can't. I don't care for the credit - but it took me so long to go though Paul Gaborit's code to get it working that I wanted to share this information. Fell free to add a comment to his answer in case my answer get's deleted again. – xamf Apr 25 '14 at 16:42
  • @xamf Thanks. I edited my answer to correct this bug. – Paul Gaborit May 19 '14 at 21:14