这个问题导致了一个新的方案的出现:
sankey
我想画一些类似的东西(不完全是):
使用 TikZ。理想情况下,我可以通过变量设置宽度并将两个流重新合并为一个。有什么好方法可以做到这一点吗?
由于这将是我的第一个 TikZ 图,我正在寻找如何解决这个问题的一般方法,而不是完整的解决方案,以便我可以有效地阅读文档。我如何分支矩形的(可变)部分?我如何将这些部分重新合并为一个?
部分解决方案:
\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}
给出:
但这些都是硬编码的。我理想情况下希望流量的宽度可变(无需手动重新计算所有点)
从语法角度来看,我可以想象这样的事情:
\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]
我正在考虑的要点
- 溪流有流向,但 tikz 应该被允许改变它(右转然后左转或反之亦然),例如在连接溪流时。
- 分层应该是这样的,第一层 == 最低层,之后的任何层都应该堆积在其上
- 直线部分或移位部分的长度/移位量可根据间距需要进行调整。
更新:
下面有一部分向右分支(\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}
现在,我们可以将另一个这样的代码片段与初始 x 和 y 以及宽度值一起添加到剩余(向下)流的末尾,然后再次向右分支。
答案1
编辑
自 TikZ/PGF 3.0 版以来,的参数atan2
已被交换。
新版本(TikZ/PGF 3.0)
这是我对新环境的第一次尝试sankeydiagram
。
它的可选参数对于修复一些全局参数很有用:
sankey tot quantity
是一个数字,代表全球流量的总量(默认值:100(代表 100%))。sankey tot length
是全局流的宽度(默认值:100pt)。sankey min radius
是每次转弯的最小半径(默认值:30pt)。sankey fill
是用于填充流程的样式。sankey draw
是用于绘制流程的样式。sankey debug
(标志)对于在构造图期间调试图很有用(默认值: false)。
环境sankeydiagram
定义了一些有用的命令来构建桑基图:
\sankeynode{prop}{angle}{name}{pos}
创建一个桑基节点(流)支柱容量(以数量单位表示),名为name
。其方向和位置由下式给出:角度和位置。\sankeynodestart
和\sankeynodeend
类似\sankeynode
(参数相同)但分别使 和 流开始 和 流结束。\sankeyadvance{name}{distance}
向前移动名为姓名。\sankeyturn{name}{angle}
将 sankey 节点命名为姓名。\sankeyfork{name}{list of forks}
分叉名为姓名。分叉列表是成对的列表:数量/名称(数量总和必须等于要分叉的sankey节点数量)。
以下是结果(先不加 ,sankey debug
然后加sankey debug
)。
现在,代码如下:
\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}
下面是另一个具有相同序言的例子(它与您的第一个例子是相同的桑基图,但具有调整后的值:工业=86.1。未经调整,总数不正确……):
然后是代码(无序言):
\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}
答案2
直接使用 tikz 的一个很好的替代方案是 Matplotlib 和 Sankey 模块。你可以在这里看到示例代码:Matplotlib Sankey 示例
我认为这是创建桑基图的更好的方法。
使用 XeLaTeX 和 matplotlib 1.2,您可以使用matplotlib-后端-pgf
使用桑基示例图并创建 pdf 图的简短代码示例:
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')
然后,您可以在 tex 文件中包括 pdf 图像:
% tex file:
\includegraphics[width=0.7\textwidth]{img/sankey.pdf}
答案3
我认为双倍距离的使用是此类绘图的关键。以下是一个小例子
\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}
显然,要发展和进步。
答案4
只是想让你知道:atan2 函数被改为pgf/tikz 3.0。 你需要每次调用 atan2 时切换 x 和 y 参数这么说吧,工作代码是:
\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}
}{
}