我正在寻找一种使用 TikZ 绘制以下图片的简单方法。也就是说,我只想指定城市的坐标,然后使用循环通过边将它们连接起来\foreach
。如果此图片上没有有向边,这将很容易:
因此,更准确地说,我的问题是如何修改\foreach
下面的第二张图片以获得上面的图片。
\begin{scope}
\foreach \x/\y/\name/\label/\where in {1.2/1.4/a/{El Paso}/below,
1/3.2/b/Albuquerque/above, 2.5/3/c/Amarillo/below, 3.8/4/d/Wichita/above,
4.3/3.3/e/Tulsa/45, 5.2/2.8/f/{Little Rock}/right, 4.2/2/g/Dallas/left,
4.8/1/h/Houston/right, 3.6/0.6/i/{San Antonio}/below} {
\draw (\x,\y) circle (.4mm);
\node[draw,circle,minimum size=2mm,inner sep=0mm,label=\where:{\footnotesize \label}] (\name) at (\x,\y) {};
}
\foreach \s/\t in {a/b, b/c, c/e, e/d, e/f, f/g, h/g, h/i}
\path[draw] (\s) edge (\t);
\end{scope}
答案1
如果您创建一个简单的“装饰”,即从起始点到终止点绘制一条笔划,则可以使用raise
装饰路径选项来获得平行路径。将shorten >
和shorten <
应用于此凸起路径,并在其上放置箭头,即可获得所需的结果:
以下是代码(样式parallel arrow
涵盖所有内容):
\documentclass{article}
\usepackage{tikz}
\begin{document}
\usetikzlibrary{decorations}
\pgfdeclaredecoration{sl}{initial}{
\state{initial}[width=\pgfdecoratedpathlength-1sp]{
\pgfmoveto{\pgfpointorigin}
}
\state{final}{
\pgflineto{\pgfpointorigin}
}
}
\tikzset{parallel arrow/.style={->,
shorten >=2mm, shorten <=2mm,
decoration={sl,raise=1mm},decorate}}
\begin{tikzpicture}
\begin{scope}
\foreach \x/\y/\name/\label/\where in {1.2/1.4/a/{El Paso}/below,
1/3.2/b/Albuquerque/above, 2.5/3/c/Amarillo/below, 3.8/4/d/Wichita/above,
4.3/3.3/e/Tulsa/45, 5.2/2.8/f/{Little Rock}/right, 4.2/2/g/Dallas/left,
4.8/1/h/Houston/right, 3.6/0.6/i/{San Antonio}/below} {
\draw (\x,\y) circle (.4mm);
\node[draw,circle,minimum size=2mm,inner sep=0mm,label=\where:{\footnotesize \label}] (\name) at (\x,\y) {};
}
\foreach \s/\t in {a/b, b/c, c/e, e/d, e/f, f/g, h/g, h/i} {
\path[draw] (\s) edge (\t);
\path[draw] (\s) edge[parallel arrow] (\t);
\path[draw] (\t) edge[parallel arrow] (\s);}
\end{scope}
\end{tikzpicture}
\end{document}
更新
我给每条路线添加了数字。我很有趣地对它们的位置进行了参数化,以便将它们全部放在一个循环中。
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{decorations,calc}
\begin{document}
\pgfdeclaredecoration{sl}{initial}{
\state{initial}[width=\pgfdecoratedpathlength-1sp]{
\pgfmoveto{\pgfpointorigin}
}
\state{final}{
\pgflineto{\pgfpointorigin}
}
}
\tikzset{parallel arrow/.style={latex-,
shorten >=2mm, shorten <=2mm,
decoration={sl,raise=1mm},decorate}}
\begin{tikzpicture}
\begin{scope}
\foreach \x/\y/\name/\label/\where in {1.2/1.4/a/{El Paso}/below,
1/3.2/b/Albuquerque/above, 2.5/3/c/Amarillo/below, 3.8/4/d/Wichita/above,
4.3/3.3/e/Tulsa/45, 5.2/2.8/f/{Little Rock}/right, 4.2/2/g/Dallas/left,
4.8/1/h/Houston/right, 3.6/0.6/i/{San Antonio}/below} {
\draw (\x,\y) circle (.4mm);
\node[draw,circle,minimum size=2mm,inner sep=0mm,label=\where:{\footnotesize \label}] (\name) at (\x,\y) {};
}
\tikzset{edge label/.style={font=\tiny, inner sep=1.6mm},
ab/.style={edge label, left},
ba/.style={edge label, right},
bc/.style={edge label, above},
cb/.style={edge label, below},
ce/.style={bc},
ec/.style={cb},
ed/.style={ab},
de/.style={ba},
ef/.style={edge label, below left, inner sep=1mm},
fe/.style={edge label, above right, inner sep=1mm},
fg/.style={edge label, above left, inner sep=1mm},
gf/.style={edge label, below right, inner sep=1mm},
hg/.style={ab},
gh/.style={ba},
hi/.style={bc},
ih/.style={cb}
}
\foreach \s/\t/\from/\to in {a/b/9/10, b/c/8/11, c/e/7/12, e/d/6/5,
e/f/13/4, f/g/14/3, h/g/15/2, h/i/16/1} {
\path[draw] (\s) edge (\t) ($(\s)!.5!(\t)$) node[\s\t] {\from} node[\t\s]{\to};
\path[draw] (\s) edge[parallel arrow] (\t);
\path[draw] (\t) edge[parallel arrow] (\s);
}
\end{scope}
\end{tikzpicture}
\end{document}
答案2
JLDiaz 的回答非常好,这只是一点补充:
我想避免对有向边的值进行硬编码,因此我想使用auto
放置选项,将节点放置在路径的一侧。然而,事实证明这只发生在 45 度步骤中:节点要么使用其south
锚点放置,要么使用其north west
锚点等放置,但没有中间值。对于此应用程序,这还不够精确。但是,如果您重新定义两个内部函数,则可以获得节点的度数精度放置:
\makeatletter
\def\tikz@auto@anchor{%
\pgfmathtruncatemacro\angle{atan2(\pgf@x,\pgf@y)-90}
\edef\tikz@anchor{\angle}%
}
\def\tikz@auto@anchor@prime{%
\pgfmathtruncatemacro\angle{atan2(\pgf@x,\pgf@y)+90}
\edef\tikz@anchor{\angle}%
}
\makeatother
为了使标签与中心线的偏移均匀,我使用了ellipse
节点形状。
\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations,calc, shapes.geometric}
\makeatletter
\def\tikz@auto@anchor{%
\pgfmathtruncatemacro\angle{atan2(\pgf@x,\pgf@y)-90}
\edef\tikz@anchor{\angle}%
}
\def\tikz@auto@anchor@prime{%
\pgfmathtruncatemacro\angle{atan2(\pgf@x,\pgf@y)+90}
\edef\tikz@anchor{\angle}%
}
\makeatother
\begin{document}
\pgfdeclaredecoration{sl}{initial}{
\state{initial}[width=\pgfdecoratedpathlength-1sp]{
\pgfmoveto{\pgfpointorigin}
}
\state{final}{
\pgflineto{\pgfpointorigin}
}
}
\tikzset{parallel arrow/.style={latex-,
shorten >=2mm, shorten <=1mm,
decoration={sl,raise=1mm},decorate}}
\begin{tikzpicture}
\begin{scope}
\foreach \x/\y/\name/\label/\where in {1.2/1.4/a/{El Paso}/below,
1/3.2/b/Albuquerque/above, 2.5/3/c/Amarillo/below, 3.8/4/d/Wichita/above,
4.3/3.3/e/Tulsa/30, 5.2/2.8/f/{Little Rock}/right, 4.2/2/g/Dallas/left,
4.8/1/h/Houston/right, 3.6/0.6/i/{San Antonio}/below} {
\draw (\x,\y) circle (.4mm);
\node[draw,circle,minimum size=2mm,inner sep=0mm,label=\where:{\footnotesize \label}] (\name) at (\x,\y) {};
}
\tikzset{
edge label/.style={
font=\tiny,
auto=right,
ellipse,inner sep=1mm,
}
}
\foreach \s/\t/\from/\to in {a/b/9/10, b/c/8/11, c/e/7/12, e/d/6/5,
e/f/13/4, f/g/14/3, h/g/15/2, h/i/16/1} {
\path[draw] (\s) edge (\t);
\path[draw] (\s) edge[parallel arrow]
node [edge label] {\from} (\t);
\path[draw] (\t) edge[parallel arrow]
node [edge label] {\to}(\s);
}
\end{scope}
\end{tikzpicture}
\end{document}
答案3
这是使用库的另一种to path
方法calc
。
一条边及其带有标签的平行箭头通过单一操作绘制to
:
\draw (from) to[edge with values=XX and YY] (to);
(其中XX
和YY
为从->到和至->从标签。它们会自动定位。
\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}
\tikzset{
% store of parameters
parallel distance/.store in=\paradist,
parallel shorten/.store in=\parashorten,
parallel label distance/.store in=\paralabdist,
% default values
parallel distance=1mm, % distance of parallel arrows from link
parallel shorten=3mm, % to shorten parallel arrows
parallel label distance=2.5mm, % distance of label from link
% main style
edge with values/.style args={#1 and #2}{
to path={
\pgfextra{
\pgfinterruptpath
% middle point
\coordinate (m) at ($(\tikztostart)!.5!(\tikztotarget)$);
% from-to arrow
\draw[-stealth,shorten >=\parashorten,shorten <=\parashorten]
($(\tikztostart)!\paradist!-90:(\tikztotarget)$)
--
($(\tikztotarget)!\paradist!90:(\tikztostart)$)
% middle of from-to arrow
coordinate[pos=.5](m from);
% label of from-to arrow
\node[font=\tiny] at ($(m)!\paralabdist!(m from)$){#1};
% to-from arrow
\draw[-stealth,shorten >=\parashorten,shorten <=\parashorten]
($(\tikztotarget)!\paradist!-90:(\tikztostart)$)
--
($(\tikztostart)!\paradist!90:(\tikztotarget)$)
% middle of to-from arrow
coordinate[pos=.5](m to);
% label of to-from arrow
\node[font=\tiny] at ($(m)!\paralabdist!(m to)$){#2};
\endpgfinterruptpath
}
% link
(\tikztostart) -- (\tikztotarget)
},
},
}
\begin{document}
\begin{tikzpicture}
\begin{scope}
\foreach \x/\y/\name/\label/\where in {%
1.2/1.4/a/{El Paso}/below,%
1/3.2/b/Albuquerque/above,%
2.5/3/c/Amarillo/below,%
3.8/4/d/Wichita/above,%
4.3/3.3/e/Tulsa/45,%
5.2/2.8/f/{Little Rock}/right,%
4.2/2/g/Dallas/left,%
4.8/1/h/Houston/right,%
3.6/0.6/i/{San Antonio}/below%
} {
\draw (\x,\y) circle (.4mm);
\node[draw,circle,minimum size=2mm,inner sep=0mm,
label=\where:{\footnotesize \label}]
(\name) at (\x,\y) {};
}
\foreach \s/\t/\from/\to in {
a/b/10/9,
b/c/11/8,
c/e/12/7,
e/d/5/6,
e/f/13/4,
f/g/14/3,
h/g/2/15,
h/i/16/1
} {
\path[draw] (\s) to[edge with values=\from{} and \to] (\t);
}
\end{scope}
\end{tikzpicture}
\end{document}