\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} }{ }