自动在所有交叉点处绘制节点(用于图形交叉)

自动在所有交叉点处绘制节点(用于图形交叉)

我使用 TikZ 绘制(无向)图,但有时我希望能够标记所有交叉点/交叉点的位置自动地。我知道 TikZintersections库有name intersections=密钥,但这需要命名图中的所有路径,然后对每对相交的路径使用该密钥(如果我需要修改图,这将非常痛苦,因为相交的边对可能会改变。)手动命名所有路径也会使代码的可读性显著降低。

问:有没有办法可以自动完成此操作?

例如,以下是彼得森图在 TikZ 中:

\documentclass{standalone}
\usepackage{tikz}
\tikzset{graph/.style={
  every node/.style={circle,fill=black,inner sep=0pt,minimum width=8pt},
  every path/.style={thick}
}}
\begin{document}
\begin{tikzpicture}[graph]
\foreach \i [evaluate=\i as \a using 90-(\i-1)*360/5] in {1,...,5} {
  \node (o\i) at (\a:2) {};
  \node (i\i) at (\a-36:0.7) {};
}
\draw (o1) -- (o2) -- (o3) -- (o4) -- (o5) -- (o1);
\draw (i1) -- (i2) -- (i3) -- (i4) -- (i5) -- (i1);
\draw (o1) -- (i3); \draw (o2) -- (i1); \draw (o5) -- (i5);
\draw[bend left] (o3) to (i4);
\draw[bend right] (o4) to (i2);
\end{tikzpicture}
\end{document}

具有 2 个交叉点的 Petersen 图

此嵌入中有两个交叉点。手动标记交叉点需要执行以下操作:

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{intersections}
\tikzset{graph/.style={
  every node/.style={circle,fill=black,inner sep=0pt,minimum width=8pt},
  every path/.style={thick},
  crossing/.style={rectangle,fill=red,inner sep=0pt,minimum size=4pt}
}}
\begin{document}
\begin{tikzpicture}[graph]
\foreach \i [evaluate=\i as \a using 90-(\i-1)*360/5] in {1,...,5} {
  \node (o\i) at (\a:2) {};
  \node (i\i) at (\a-36:0.7) {};
}
\draw (o1) -- (o2) -- (o3) -- (o4) -- (o5) -- (o1);
\draw[name path=p1] (i1) -- (i2) -- (i3) -- (i4) -- (i5) -- (i1);
\draw[name path=p2] (o1) -- (i3);
\draw (o2) -- (i1); \draw (o5) -- (i5);
\draw[name path=p3,bend left] (o3) to (i4);
\draw[name path=p4,bend right] (o4) to (i2);
\path [name intersections={of=p1 and p2}] (intersection-1) node[crossing] {};
\path [name intersections={of=p3 and p4}] (intersection-1) node[crossing] {};
\end{tikzpicture}
\end{document}

输出结果如下:

标记有 2 个交叉点的 Petersen 图

您可能可以想象,对于更复杂的图形,这种方法会遇到可读性问题;有时我最终不得不命名绘图中的几乎所有路径和name intersections数十对路径。理想的情况是,如果我可以只将类似的东西添加到环境样式showcrossingstikzpicture并让它自动发生。

我将发布我自己的解决方案,但如果有人可以改进我的方法或有不同的方法,我会感兴趣!

答案1

这是我的解决方案。希望其他人会觉得它有用。

归结为:自动命名图形中的每条路径,然后遍历每对命名路径并将节点放置在发现的任何交叉点处。

我在此过程中发现了一些问题:

  1. 起初我想到使用every path/.style={...}或某物来自动命名边。不幸的是,\node我用来创建顶点的命令最终会隐式创建额外的路径,而我们不想找到与这些路径的交点。此外,对于更复杂的图形,我有时会使用(未绘制的)路径来放置顶点或辅助坐标,如果我使用 ,这些会干扰every path。为了解决这个问题,我定义了一个\edge命令,用它来替换我绘制的所有\path命令。它与 相同,\draw但应用了一种edge我们可以定义的样式。

    \newcommand*\edge[1][]{\draw[edge,#1]}
    
  2. 我们需要一个计数器来记录绘图中命名路径的数量。趁着这个机会,我们再创建一个计数器,这样我们就可以存储找到的交叉点数量,以防我们出于某种原因对此感兴趣(例如,我们可以在文档中使用该输出)。

    \newcounter{ngraphpaths}
    \newcounter{ncrossings}
    
  3. 在定义showcrossings样式时,我们使每条边增加计数器并使用name path global。我最终使用了全局,因为否则,如果在\foreach或其他代码块内添加边,它们在我们去寻找交点的图片块末尾是不可见的。我假设这cross_p是一个足够独特的前缀,不会与其他东西发生冲突。我还检查了,这甚至适用于同一文件中的多个图形;库intersections只做了一个\global\let,所以覆盖以前的名字没有问题。

    edge/.append style={increment_ngraphpaths,name path global={cross_p\the\value{ngraphpaths}}},
    increment_ngraphpaths/.code={\addtocounter{ngraphpaths}{1}},
    

    我们还需要确保在每张图片开始时重置计数器:

    execute at begin picture={\setcounter{ngraphpaths}{0}\setcounter{ncrossings}{0}},
    
  4. 最后,在图片的末尾,如果至少有 2 条路径具有边缘样式,我们会迭代每个可能的对(相等对除外* - 请参阅下面的注释)并寻找交点。令人惊讶的是,尽管是 O(n²),但我并没有发现这对性能有太大的影响。请注意,在尝试使用 迭代它们之前,我们必须检查至少有一个交点\foreach。出于记录目的,我们还将找到的交叉点数量打印到控制台 - 这在实践中很有用,因为在尝试绘制图形时,我们通常已经知道交叉点的数量应该是多少。

    execute at end picture={%
    \ifnum \value{ngraphpaths}>1
      \pgfmathtruncatemacro\aend{\value{ngraphpaths}-1}
      \foreach \a in {1,...,\aend}{
        \pgfmathtruncatemacro\bstart{\a+1}
        \foreach \b in {\bstart,...,\value{ngraphpaths}}
          \path [name intersections={of={cross_p\a} and {cross_p\b},name=i,total=\t}]
            \ifnum \t>0
              \foreach \s in {1,...,\t}{(i-\s) node[crossing] {}}
              \pgfextra{\addtocounter{ncrossings}{\t}}
            \fi;
      }
    \fi
    \typeout{showcrossings: found \the\value{ncrossings} crossing(s)}}
    

综合起来:

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{intersections}
\tikzset{graph/.style={
  every node/.style={circle,fill=black,inner sep=0pt,minimum width=8pt},
  edge/.style={thick},
  crossing/.style={rectangle,fill=red,inner sep=0pt,minimum size=4pt}
}}
\newcommand*\edge[1][]{\draw[edge,#1]}
\newcounter{ngraphpaths}
\newcounter{ncrossings}
\tikzset{showcrossings/.style={
  edge/.append style={increment_ngraphpaths,name path global={cross_p\the\value{ngraphpaths}}},
  increment_ngraphpaths/.code={\addtocounter{ngraphpaths}{1}},
  execute at begin picture={\setcounter{ngraphpaths}{0}\setcounter{ncrossings}{0}},
  execute at end picture={%
\ifnum \value{ngraphpaths}>1
  \pgfmathtruncatemacro\aend{\value{ngraphpaths}-1}
  \foreach \a in {1,...,\aend}{
    \pgfmathtruncatemacro\bstart{\a+1}
    \foreach \b in {\bstart,...,\value{ngraphpaths}}
      \path [name intersections={of={cross_p\a} and {cross_p\b},name=i,total=\t}]
        \ifnum \t>0
          \foreach \s in {1,...,\t}{(i-\s) node[crossing] {}}
          \pgfextra{\addtocounter{ncrossings}{\t}}
        \fi;
  }
\fi
\typeout{showcrossings: found \the\value{ncrossings} crossing(s)}}
}}
\begin{document}
\begin{tikzpicture}[graph,showcrossings]
\foreach \i [evaluate=\i as \a using 90-(\i-1)*360/5] in {1,...,5} {
  \node (o\i) at (\a:2) {};
  \node (i\i) at (\a-36:0.7) {};
}
\edge (o1) -- (o2) -- (o3) -- (o4) -- (o5) -- (o1);
\edge (i1) -- (i2) -- (i3) -- (i4) -- (i5) -- (i1);
\edge (o1) -- (i3);
\edge (o2) -- (i1); \edge (o5) -- (i5);
\edge[bend left] (o3) to (i4);
\edge[bend right] (o4) to (i2);
\end{tikzpicture}
\end{document}
*关于自交叉路径的说明

在上面的代码中,我们从未检查命名路径cross_p\a与和cross_p\b的相交值是否相等。尽管可以创建一条与自身相交的路径。如果这样做,上面的代码将无法检测到交叉点。如果重写 foreach 循环,使开始于而不是,则可以找到这些交叉点,但在我的测试中,这仅适用于直线路径。一旦有一些弯曲的路径,它就会中断,并且交叉点库将找到数百个误报交叉点。\a\b\edge (1) -- (2) -- (3) ...\b\a\a+1

简而言之,如果您使用此功能,请确保每个\edge命令在路径上不包含任何自交叉。

相关内容