TIKZ - 两条路径交叉时自动架桥

TIKZ - 两条路径交叉时自动架桥

我正在为我们正在使用的系统创建依赖关系图,以显示什么会对什么产生影响。这很好用,但我有很多交叉点(可能还会有更多),这可能会使阅读和跟踪变得非常复杂。

在此处输入图片描述

我现在正尝试为这些交叉点画桥,类似于TikZ 中两条线的交点实际上并未连接,而是自动化的,因为否则会花费太多时间。

\documentclass[a4paper]{scrartcl} [10pt,letterpaper]
\usepackage{tikz}
\usetikzlibrary{calc,shapes.geometric, arrows,positioning}
\usepackage{xcolor}
\begin{document}
\begin{figure}
\tikzstyle{ele} = [rectangle, rounded corners, minimum width=2.427cm, minimum height=1.5cm, align=center, draw=black, fill=yellow!50]
\begin{tikzpicture}[node distance=3.927cm]
    %Nodes
    \node (Person) [ele] {Person};
    \node (Club) [ele, right =of Person] {Club};
    \node (Member) [ele, below = of Person] {Member};
    \node (Bal) [ele, right = of Club] {Balance};
    \node (MP) [ele, below = of Bal] {Membership \\ Package};
    \node (Group) [ele, below = of Club] {Group};
    \node (Level) [ele, below = of MP] {Level};
    \node (Rec) [ele, below = of Group] {Recognition};
    \node (Pref) [ele, below = of Member] {Preference};
    %Lines
    \path (Person) edge [->,thick] (Member);
    \path (Member) edge [->,thick] (Group);
    \path (Pref) edge [->,thick] (Group);
    \path (Group) edge [<->,thick] (Rec);
    \path (Club) edge [->,thick] (Level);
    \path (MP) edge [->,thick] (Level);
    \path (Group) edge [<->,thick] (Level);
    \path (Club) edge [->,thick] (MP);
    \path (Group) edge [->,thick] (MP);
    \path (Group) edge [<->,thick] (Bal);
    \path (Club) edge [->,thick] (Bal);
    \path (Bal) edge [->,thick] (Rec);
    \path (Club) edge [->,thick] (Member);
    \path (MP) edge [->,thick,bend left=45] (Member);
    \path (Club) edge [->,thick] (Group);
    \path (MP) edge [->,thick] (Bal);
    \path (Club) edge [->,thick,bend right=45] (Rec);
    \path (Level) edge [->,thick] (Rec);
    \path (Member) edge [->,thick,bend right=20] (Rec);
    \path (MP) edge [->,thick] (Person);
\end{tikzpicture}
\end{figure}
\end{document}

如果有人有更好的想法,我也愿意接受这些依赖关系的其他彻底展示。

答案1

以下是使用最新版本的解决方案spath3库(在撰写本文时需要开发版本,但经过更多测试后将上传到 CTAN)。

我已经对代码进行了注释以解释发生了什么,但基本思想如下:

  1. 定义并保存所有边缘
  2. 遍历所有边对以找到交点,并在该点的其中一条边中插入断点。
  3. 加宽这些间隙并在间隙中插入一个圆弧,确保圆弧指向“向上”(这是需要开发版本的部分)。
  4. 现在重新遍历所有边对并再次找到交点(由于插入了圆弧,这些交点自之前以来已经移动了),并在其他小路。
  5. 将这些间隙稍微加宽一些。
  6. 渲染所有路径。

我稍微修改了路径以确保没有三重交叉(因为添加弧时它们看起来很愚蠢)。

结果如下:

以桥梁形式呈现交叉口的图表

代码如下:

\documentclass[a4paper]{scrartcl} [10pt,letterpaper]
%\url{https://tex.stackexchange.com/q/414124/86}
\usepackage{tikz}
\usetikzlibrary{
  spath3,
  intersections,
  calc,
  shapes.geometric,
  arrows,positioning
}
\usepackage{xcolor}

% We're going to do a lot of iterating over the edges so we define a
% comma-separated list of them.
\def\Edges{%
Person/Member,%
Member/Group,%
Pref/Group,%
Group/Rec,%
Club/Level,%
MP/Level,%
Group/Level,%
Club/MP,%
Group/MP,%
Group/Bal,%
Club/Bal,%
Bal/Rec,%
Club/Member,%
MP/Member,%
Club/Group,%
MP/Bal,%
Club/Rec,%
Level/Rec,%
Member/Rec,%
MP/Person%
}

\begin{document}
\begin{figure}
\tikzstyle{ele} = [rectangle, rounded corners, minimum width=2.427cm, minimum height=1.5cm, align=center, draw=black, fill=yellow!50]
\begin{tikzpicture}[node distance=3.927cm]
    %Nodes
    \node (Person) [ele] {Person};
    \node (Club) [ele, right =of Person] {Club};
    \node (Member) [ele, below = of Person] {Member};
    \node (Bal) [ele, right = of Club] {Balance};
    \node (MP) [ele, below = of Bal] {Membership \\ Package};
    \node (Group) [ele, below = of Club] {Group};
    \node (Level) [ele, below = of MP] {Level};
    \node (Rec) [ele, below = of Group] {Recognition};
    \node (Pref) [ele, below = of Member] {Preference};

% Most of the edges will be lines, so we define them all as lines initially
% and then overwrite the ones that are curves.
% As we're defining them in a \foreach loop we have to work globally.
\foreach \source/\target in \Edges {
  \path[spath/save global=\source-\target] (\source) -- (\target);
}
% Now overwrite the ones that are meant to be curved
\path[spath/save global=MP-Member] (MP) to[bend left=45] (Member);
\path[spath/save global=Club-Rec] (Club) to[bend right=45] (Rec);
\path[spath/save global=Member-Rec] (Member) to[bend right=20] (Rec);
% It is best to avoid triple intersections so these two are also
% made into curves (they were straight in the original diagram)
\path[spath/save global=Group-Bal] (Group) to[bend left=20] (Bal);
\path[spath/save global=Bal-Rec] (Bal) to[bend left=5] (Rec);

% We now iterate through the list of edges and break each where it
% intersects with others.  For each pair we only want to break
% one of the edges, so when we consider an edge we only intersect
% it with edges that came earlier in the list.  To achieve this,
% after examining an edge we add it to the list \PreEdges that
% and we intersect each edge only with those in \PreEdges
\def\PreEdges{}
\foreach \sourceA/\targetA in \Edges
{
  \foreach \sourceB/\targetB in \PreEdges
  {
    % Split the first path where it meets the second path
    \tikzset{
      spath/split globally at intersections with={\sourceA-\targetA}{\sourceB-\targetB}
    };
  }
  % Now add the first path to the list of paths to intersect against
  % The \if is so that we don't get an empty entry at the start of
  % the list.
  % (This would be a bit simpler in LaTeX3)
  \if\PreEdges\relax\relax
  \xdef\PreEdges{%
    \sourceA/\targetA
  }
  \else
  \xdef\PreEdges{%
    \PreEdges,%
    \sourceA/\targetA
  }
  \fi
}

% At the intersection breaks we want to make a small gap and insert
% an arc.  This next line defines the arc (it will be scaled and
% transformed to fit in the gap so the actual size doesn't matter)
\path[spath/save=arc] (0,0) arc[radius=1cm, start angle=180, delta angle=-180];

% Now we iterate through the paths, adding gaps and then splicing in
% the arc.
\foreach \source/\target in \Edges
{
  \tikzset{
    spath/insert gaps globally after components={\source-\target}{8pt},
    spath/join components globally upright with={\source-\target}{arc},
  }
}

% This deals with the over paths, now we need to break the under paths
% where they intersect with the (new) over paths.  So we do our
% intersection double loop again, only with the paths in the opposite
% order in the splitting command.
\def\PreEdges{}
\foreach \sourceA/\targetA in \Edges
{
  \foreach \sourceB/\targetB in \PreEdges
  {
    \tikzset{
      spath/split globally at intersections with={\sourceB-\targetB}{\sourceA-\targetA}
    };
  }
  \if\PreEdges\relax\relax
  \xdef\PreEdges{%
    \sourceA/\targetA
  }
  \else
  \xdef\PreEdges{%
    \PreEdges,%
    \sourceA/\targetA
  }
  \fi
}

% Our last loop inserts gaps in the new breaks and renders each edge.
\foreach \source/\target in \Edges
{
  \tikzset{
    spath/insert gaps after components={\source-\target}{4pt},
  }
  \draw[spath/use=\source-\target,thick,->];
}
\end{tikzpicture}
\end{figure}
\end{document}

相关内容