253 lines
6.8 KiB
TeX
253 lines
6.8 KiB
TeX
|
|
||
|
\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}
|
||
|
}{
|
||
|
}
|