构建类似 dag 的图表

构建类似 dag 的图表

我想画出类似这样的图。从数学上讲,你可以将它们描述为带有悬垂弧的有向无环图。它们有点让人想起路线图

期望输出

在此处输入图片描述

来源这里。图的顶部有四条悬垂的线,然后有四个节点对这些线进行操作,最后有三条悬垂的线从图的底部伸出。我知道如何在 TikZ 中创建这个特定的图表,但我想制作一个用于创建它们的包。

所需输入

对我来说,重要的是:

  • 用户需要指定尽可能少的内容,以便于创建和修改大图表,并且
  • 该图是按逐行方式组成的。

这是我要的代码喜欢我的用户输入,以便生成上面的图片(我在下一小节中解释代码):

\begin{wickersondiagram}[%
  initial lines={a/{pos=10}, b/{pos=30}, c/{pos=50}, d/{pos=70}}]
\step[left=0, right=40, text=decide,%
  incoming lines={a,b},%
  outgoing lines={e/{pos=10,label=yes}, f/{pos=30,label=no}}]
\\
\step[left=20, right=60,%
  pale,%
  incoming lines={f,c},%
  outgoing lines={g/{pos=30},h/{pos=50}}]
\\
\step[left=0, right=40, text=join,%
  incoming lines={e,g},%
  outgoing lines={i/{pos=20}}]
\step[left=60, right=80,%
  incoming lines={d},%
  outgoing lines={j/{pos=70}}]
\\
\end{wickersondiagram}

我所说的“逐行”是指,如果我从该代码中删除最后几行,我仍然会获得一张完全有效的图片,它看起来就像上面的图片,但缺少了下部。

所需输入的解释

用户通过指定 的水平位置来启动图表。initial lines每条线都分配有一个名称,这里是ab和。在构建图表时,我的包应该保持一个隐式“状态”,其中包含线条的名称及其位置。在构建的这一点上,状态是cd

{a ↦ 10, b ↦ 30, c ↦ 50, d ↦ 70}.

然后用户创建一个\step。水平位置用left和键指定。步骤上right有可选的,在本例中是“决定”。从状态中移除,并将添加到状态中。此时状态变为:textincoming linesoutgoing lines

{e ↦ 10, f ↦ 30, c ↦ 50, d ↦ 70}.

维护状态的目的是,无需用户不断指定,即可绘制与特定步骤无关的线条。例如,包应该隐式地在“决定”步骤的右侧绘制线条c和。d

有时两个或多个步骤会同时发生,如上图底部所示。我建议在语法中通过使用\\明确开始新的“行”步骤来处理此问题。

我目前的方法

到目前为止,我的方法是尝试将状态存储在一系列宏中,例如:

\def\state@line@a{10}
\def\state@line@b{30}
\def\state@line@c{50}
\def\state@line@d{70}
\def\state@line@e{10}
\def\state@line@f{30}

然后维护当前处于该状态的行的逗号分隔列表,例如:

\def\activelines{e,f,c,d}

我之前的两个问题(这里这里),这是我尝试让这种方法发挥作用的结果。在这些问题中,我将代码压缩为最小的工作示例,以展示我面临的特定技术问题。我感谢回答者如此彻底和周到地解决这些问题。但是,由于每次都是同一个人回答我的问题,我怀疑我最好直奔主题,展示我真正在做什么。我仍然对我的问题进行了一些理想化,以便于解释,但我希望保留了大部分技术挑战。

扩展

我希望最终将我的图表扩展为比上面示例更复杂的图表。例如:

  • 我希望我的线条具有可定制的粗细(因此“状态”的元素实际上应该是元组而不是单个整数)。

  • 我想添加一个命令来“扭曲”两条或多条线,交换它们的水平位置。

  • 用户当前必须为每个步骤指定一个leftright位置,但我最终希望根据传入和传出线的位置自动计算这些。

  • 我想添加包含图表的特殊“复合”节点。

  • 我想添加另一种类型的线。

  • 我想添加一个命令来拍摄当前“状态”的快照,以及一个命令来恢复以前的“状态”。

因此,对我来说,重要的是您的解决方案足够强大且易于理解,以便我可以对它们进行大量摆弄!

答案1

借助几个子问题[12345],我设法拼凑出了一个合理的解决方案。

用户输入已略作修改:现在可以\row在每行的开头而不是\\结尾输入。

这是我的代码:

\documentclass{article}

\usepackage{tikz}
\usepackage{etextools}

\makeatletter

\pgfkeys{/wickerson/.cd,
  execute style/.style = {#1},
  execute macro/.style = {execute style/.expand once=#1},
  pos/.code=           {\xdef\wickerson@pos{#1}},
  left/.code=          {\xdef\wickerson@left{#1}},
  right/.code=         {\xdef\wickerson@right{#1}},
  label/.code=         {\xdef\wickerson@label{#1}},
  text/.code=          {\xdef\wickerson@text{#1}},
  pale/.code=          {\xdef\wickerson@color{red!50}},
  scale/.code=         {\xdef\wickerson@scale{#1}},
  initial lines/.code= {\xdef\wickerson@initiallines{#1}},
  incoming lines/.code={\xdef\wickerson@incominglines{#1}},
  outgoing lines/.code={\xdef\wickerson@outgoinglines{#1}},
}
\def\wickerson@color{red}

\newcommand*\@defineLine[2]{%
  %\typeout{Defining wickerson@#1@pos = #2}
  \expandafter\xdef\csname wickerson@#1@pos\endcsname{#2}
}

% Project the pos-field from the line with the given
% identifier. For instance, if \wickerson@foo@pos=35 then
%   \@getLinePos{foo}
% will give 35.
\gdef\@getLinePos#1{\csname wickerson@#1@pos\endcsname}


% For tracking the distance from the top of the diagram.
\newcounter{VCursor}

% Default step height
\newcommand{\stepHeight}{10}

% Height of the current row.
\newcounter{RowHeight}


% A comma-separated list of active lines. Each element is a line identifier. 
\gdef\@activeLines{}

% Add the given identifier to the list of active lines.
\newcommand*\@addToActiveLines[1]{%
  \ifx\@activeLines\@empty\else\g@addto@macro\@activeLines{,}\fi
  \g@addto@macro\@activeLines{#1}
}

\newcommand*\@removeFromActiveLines[1]{%
  \@expandtwoargs\@removeelement{#1}\@activeLines\@activeLines
  \global\let\@activeLines=\@activeLines
}

\newcommand*\row[1][20]{%
  \setcounter{RowHeight}{#1}
  \addtocounter{VCursor}{\theRowHeight}%
  \typeout{Starting row. Drawing lines \@activeLines}
  \foreach\i in \@activeLines {%
    \draw[black] (\@getLinePos\i,\theVCursor) 
      -- (\@getLinePos\i,\theVCursor-\theRowHeight);
    \node[text=black!25,anchor=west] at
      (\@getLinePos\i,\theVCursor-\theRowHeight+5) {\strut \i};
  }
}

\newcommand*\@printState{%
  \typeout{Current state:}
  \foreach\i in \@activeLines {%
    \typeout{  (\i, \@getLinePos\i)}
  }
  \typeout{Current activeLines:}
  \show\@activeLines
}

\newcommand*\@drawLineLabel[2]{%
  \node[text=black,anchor=east] at
    (#1,\theVCursor+5) {\strut #2};
}

\newcommand*\step[1][]{%
  \typeout{Starting step.}
  \pgfkeys{/wickerson/.cd,left=0,right=0,text={},%
    incoming lines={},outgoing lines={},#1}
  \fill[\wickerson@color] (\wickerson@left,\theVCursor-\stepHeight) 
    rectangle (\wickerson@right,\theVCursor);
  \gdef\wickerson@color{red}
  \node at (\wickerson@left*0.5 + \wickerson@right*0.5, 
    \theVCursor - 0.5*\stepHeight) {\wickerson@text};
  \foreach\i in \wickerson@incominglines {
    \typeout{Removing line \i}
    \ExpandNext\@removeFromActiveLines\i
  }
  \foreach \i/\values in \wickerson@outgoinglines {
    \pgfkeys{/wickerson/.cd,pos=0,label={},execute macro=\values}
    \typeout{Adding (\i,\wickerson@pos) to the state.}
    \ExpandNextTwo\@defineLine\i\wickerson@pos
    \ExpandNext\@addToActiveLines\i
    \ExpandNextTwo\@drawLineLabel\wickerson@pos\wickerson@label
  } 
  \@printState
}

\newenvironment{wickersondiagram}[1][]{%
  \typeout{Starting diagram.}
  \pgfkeys{/wickerson/.cd,scale=1,initial lines={},#1}
  \setcounter{VCursor}{0}%
  \begin{tikzpicture}[x=1mm,y=-1mm]
    \foreach \i/\values in \wickerson@initiallines {
      \pgfkeys{/wickerson/.cd,pos=0,label={},execute macro=\values}
      \typeout{Adding (\i,\wickerson@pos) to the state.}
      \ExpandNextTwo\@defineLine\i\wickerson@pos
      \ExpandNext\@addToActiveLines\i
      \@printState
    } 
}{%
  \end{tikzpicture}
}

\makeatother

\begin{document}

\begin{wickersondiagram}[%
  initial lines={a/{pos=10}, b/{pos=30}, c/{pos=50}, d/{pos=70}}]
\row
\step[left=0, right=40, text=decide,%
  incoming lines={a,b},%
  outgoing lines={e/{pos=10,label=yes}, f/{pos=30,label=no}}]
\row
\step[left=20, right=60,%
  pale,%
  incoming lines={f,c},%
  outgoing lines={g/{pos=30},h/{pos=50}}]
\row
\step[left=0, right=40, text=join,%
  incoming lines={e,g},%
  outgoing lines={i/{pos=20}}]
\step[left=60, right=80,%
  incoming lines={d},%
  outgoing lines={j/{pos=70}}]
\row[10]
\end{wickersondiagram}

\end{document}

这是输出。灰色字母是赋予行的内部名称,用于调试目的。

在此处输入图片描述

我鼓励任何人改进此代码,如果他们愿意的话。因此,我做出了这个回答社区维基

相关内容