在一些数学图形中,沿路径平滑地改变曲线厚度是很好的。这是为了再现用墨线绘制的手绘数学图形。
\documentclass{article}\usepackage{tikz}
\def\ltrstroke #1,#2 #3,#4 #5,#6 #7,#8%
{\foreach \n in {0,0.01,0.02,...,0.09}{\path[line width=1pt,rounded corners=48pt,line cap=round,draw]%
(#1+\n/1,#2+\n/1)--(#3+\n/4,#4+\n/4)--(#5+\n/3,#6-\n/3)--(#7+\n/4,#8-\n/4);}}
\def\ltrinking #1,#2 #3,#4 #5,#6 #7,#8%
{\foreach \n in {0,0.01,0.02,...,0.09}{\path[line width=2pt,rounded corners=48pt,draw]%
(#1+\n/.4,#2+\n/.4)--(#3+\n/.6,#4+\n/.6)--(#5+\n/1,#6-\n/1)to[bend left](#7+\n/4,#8-\n/4)--cycle;}\path[line width=2pt,rounded corners=48pt](#1,#2)--(#3,#4)--(#5,#6)to[bend left](#7,#8)--cycle;}
\begin{document}\begin{tikzpicture}[bend angle=8];
\ltrinking 0,0 4,4 8,3 4,-1
\ltrstroke 0,5 0.2,5.2 3,8 2,6
\end{tikzpicture}\end{document}
执行此操作的一个程序是通过多次添加偏差来简单地扩展路径坐标。这些偏差的增加或减少取决于它们在线上的位置。此外,这是通过一组偏差递归完成的。我们绘制了许多重叠的曲线。结果如上所示。
问题:问题是
\def
接受 <10 个输入。那么,如何扩展\def
任意偶数个参数 >10,即另一个数字的倍数?我们需要它来控制此类绘图中的线条。每个x
和参数都由 for each 循环分别转换为手动固定数值参数sy
的函数。p
请建议如何以元组形式输入坐标,而不是以 {x1}{y1}{p1}{x2}{y2}{p2} 的形式输入。当所有输入都给定数字并且曲线经过编辑时,这会变得令人困惑,并且如果输入错误,它会变得模棱两可。将其视为|x,y,p|
一个参数,提取x
和y
和p
。
在 TeX 和 MetaPost 中,笔确实会形成不同宽度的路径:
\documentclass{article}\usepackage[shellescape,latex]{gmp}
\begin{document}\begin{figure}\centering\begin{mpost}
pen mypen; mypen = pencircle scaled 1in xscaled .08 yscaled .32 rotated 180;
pickup mypen; draw (0,0)..(100,-32)..(192,192)..(256,64);
\end{mpost}\caption{testing}\end{figure}\end{document}
Tikz 更适合将其与方程式工作相结合,并且输出速度更快。它还允许将其他输出的部分视为图形节点,并且拥有强大的库。Metapost 最适合方程式定义的笔和曲线,或者当曲线的末端需要尖锐和旋转时。但是,每个粗笔转弯的贝塞尔参数都需要调整,并且与每条曲线共变。我在这里的实现最多需要调整一个参数。
来自答案的 n 参数宏这个问题很棒。我试图概括这种用法的答案;但注释掉的版本给出了未定义的控制序列错误。未注释的版本有效;代码\csname x{#1} \endcsname
可能无效。用什么来代替它?
\documentclass{article}\usepackage{tikz}
\newcount\tmpnum
\def\scanargs #1#2#3;{\let\tmp=#1\tmpnum=0 \scanargsA #3|,,|}
\def\scanargsA #1,#2,#3|{\ifx,#1,\expandafter\tmp \else
\advance\tmpnum by1
\expandafter\def\csname x:\the\tmpnum\endcsname{#1}%
\expandafter\def\csname y:\the\tmpnum\endcsname{#2}%
\expandafter\def\csname z:\the\tmpnum\endcsname{#3}%
\expandafter\scanargsA \fi}
\def\x#1{\csname x:#1\endcsname}
\def\y#1{\csname y:#1\endcsname}
\def\z#1{\csname z:#1\endcsname}
\def\arcpart#1{({\csname x#1 \endcsname}+\n/{\csname z#1 \endcsname},{\csname y#1 \endcsname}-\n/{\csname z#1 \endcsname})\to}
\def\ltrinking{\foreach \n in {0,.01,.02,...,1}{%
\path[line width=2pt,rounded corners=48pt,draw]%
(\x1+\n/\z1,\y1+\n/\z1)--(\x2+\n/\z2,\y2+\n/\z2)--(\x3+\n/\z3,\y3-\n/\z3)--(\x4+\n/\z4,\y4-\n/\z4)--(\x5+\n/\z5,\y5-\n/\z5)--(\x6+\n/\z6,\y6-\n/\z6)--cycle;%
}}
\begin{document}\begin{tikzpicture}[bend angle=8];
\scanargs\ltrinking|0,8,3|5,4,1|16,9,4|8,4,1|5,1,4|4,-8,1;
% \scanargs\ltric|7,0,0|0,8,3|5,4,1|16,9,4|8,4,1|5,1,4|4,-8,1;
\end{tikzpicture}\end{document}
答案1
您可以使用宏\scanargs \macro x1,y1 x2,y2 ... xn,yn;
,然后可以在您的\macro
格式中使用扫描的参数\x1
,,,\x2
... \x9, \y9
,但是\x{10}, \y{22}
等等。我使用您的示例展示了示例:
\documentclass{article}\usepackage{tikz}
\newcount\tmpnum
\def\scanargs #1#2;{\let\tmp=#1\tmpnum=0 \scanargsA #2 {},{} }
\def\scanargsA #1,#2 {\ifx,#1,\expandafter\tmp \else
\advance\tmpnum by1
\expandafter\def\csname x:\the\tmpnum\endcsname{#1}%
\expandafter\def\csname y:\the\tmpnum\endcsname{#2}%
\expandafter\scanargsA \fi
}
\def\x#1{\csname x:#1\endcsname}
\def\y#1{\csname y:#1\endcsname}
\def\ltrinking {\foreach \n in {0,0.01,0.02,...,0.09}
{\path[line width=2pt,rounded corners=48pt,draw]
(\x1+\n/.4,\y1+\n/.4)--(\x2+\n/.6,\y2+\n/.6)--(\x3+\n/1,\y3-\n/1)to[bend left]
(\x4+\n/4,\y4-\n/4)--cycle;}
\path[line width=2pt,rounded corners=48pt]
(\x1,\y1)--(\x2,\y2)--(\x3,\y3)to[bend left](\x4,\y4)--cycle;
}
\def\ltrstroke {\foreach \n in {0,0.01,0.02,...,0.09}
{\path[line width=1pt,rounded corners=48pt,line cap=round,draw]
(\x1+\n/1,\y1+\n/1)--(\x2+\n/4,\y2+\n/4)--(\x3+\n/3,\y3-\n/3)--(\x4+\n/4,\y4-\n/4);}
}
\begin{document}\begin{tikzpicture}[bend angle=8];
\scanargs\ltrinking 0,0 4,4 8,3 4,-1;
\scanargs\ltrstroke 0,5 0.2,5.2 3,8 2,6;
\end{tikzpicture}
\end{document}
编辑:egreg 的回答中有一个小小的“附加值”:\newdrawingcommand
声明器。您可以在代码中\newdrawingcommand\lrstroke{...}
简单地使用,而无需明确使用。如果您喜欢这样的功能,那么可以通过以下方式实现:\lrstroke arguments
\scanarg
\def\newdrawingcommand#1{%
\edef#1{\noexpand\scanargs\csname s:\string#1\endcsname}%
\expandafter\def\csname s:\string#1\endcsname
}
答案2
一种实现expl3
,其中我定义了一个\newdrawingcommand
,它以命令名称和替换文本作为参数;可以选择\foreach
添加基于的命令,以实现更大的灵活性。
在替换文本中,可以通过\x
和引用各个点\y
;这些宏仅在那里可用(它们不会破坏其他现有定义)。
\documentclass{article}
\usepackage{xparse}
\usepackage{tikz}
\ExplSyntaxOn
\NewDocumentCommand{\newdrawingcommand}{m O{\guidoforeach} m}
{
\cs_new_protected:Npn #1 ##1;
{
\group_begin:
\cs_set_eq:NN \x \guido_x_coord:n
\cs_set_eq:NN \y \guido_y_coord:n
\guido_parse_arg:n { ##1 }
#2 { #3 }
\group_end:
}
}
\seq_new:N \l_guido_arg_list_seq
\seq_new:N \l_guido_x_list_seq
\seq_new:N \l_guido_y_list_seq
\cs_new_protected:Npn \guido_parse_arg:n #1
{
\seq_clear:N \l_guido_x_list_seq
\seq_clear:N \l_guido_y_list_seq
\seq_set_split:Nnn \l_guido_arg_list_seq { ~ } { #1 }
\seq_map_inline:Nn \l_guido_arg_list_seq
{
\tl_if_blank:nF { ##1 }
{% the last item is empty if ; is preceded by a space
\seq_put_right:Nx \l_guido_x_list_seq { \clist_item:nn { ##1 } { 1 } }
\seq_put_right:Nx \l_guido_y_list_seq { \clist_item:nn { ##1 } { 2 } }
}
}
}
\cs_new:Npn \guido_x_coord:n #1
{
\seq_item:Nn \l_guido_x_list_seq { #1 }
}
\cs_new:Npn \guido_y_coord:n #1
{
\seq_item:Nn \l_guido_y_list_seq { #1 }
}
\ExplSyntaxOff
\newcommand{\guidoforeach}[1]{%
\foreach \n in {0,0.01,0.02,...,0.09}{#1}%
}
\newcommand{\guidoforeachdouble}[1]{%
\foreach \n in {0,0.01,...,0.36}{#1}%
}
\newdrawingcommand{\ltrstroke}[\guidoforeachdouble]{%
\path[
line width=1pt,rounded corners=48pt,line cap=round,draw
]
(\x{1}+\n/1,\y{1}+\n/1)--
(\x{2}+\n/2,\y{2}+\n/2)--
(\x{3}+\n/3,\y{3}-\n/3)--
(\x{4}+\n/4,\y{4}-\n/4);
}
\newdrawingcommand{\ltrinking}{%
\path[
line width=2pt,rounded corners=48pt,draw
](\x{1}+\n/.4,\y{1}+\n/.4)--
(\x{2}+\n/.6,\y{2}+\n/.6)--
(\x{3}+\n/1,\y{3}-\n/1) to
[bend left](\x{4}+\n/4,\y{4}-\n/4)--cycle;
\path[
line width=2pt,rounded corners=48pt
](\x{1},\y{1})--(\x{2},\y{2})--(\x{3},\y{3}) to
[bend left](\x{4},\y{4})--cycle;
}
\begin{document}
\begin{tikzpicture}[bend angle=8];
\ltrinking 0,0 4,4 8,3 4,-1 ;
\ltrstroke 0,5 0.2,5.2 3,8 2,6 ;
\end{tikzpicture}
\end{document}
该\guido_parse_arg:n
函数将参数(在空格处)拆分为以逗号分隔的对(在空格处),该参数应以尾随分号结束;然后将对中的每个项目添加到 x 坐标列表或 y 坐标列表中。调用\x{
钾}
将访问 x 坐标钾第点,对于 也类似\y
。
在 的定义中,\ltrstroke
我使用了不同的\foreach
循环,只是为了展示用法。如果删除可选参数,循环\foreach
默认为\guidoforeach
。这可能对调试有用,而无需修改主替换文本。
在绘图宏的参数中,分号前可以有一个尾随空格,但逗号周围不能有空格。
通过改变语法,我们可以适应n-元组,其中n是任意的。我已经用第一个命令展示了示例,其中四个点被指定为两个四元组。
\documentclass{article}
\usepackage{xparse}
\usepackage{tikz}
\ExplSyntaxOn
\NewDocumentCommand{\newdrawingcommand}{m O{\guidoforeach} m}
{
\cs_new_protected:Npn #1 ##1;
{
\group_begin:
\cs_set_eq:NN \x \guido_x_coord:n
\cs_set_eq:NN \y \guido_y_coord:n
\cs_set_eq:NN \z \guido_z_coord:n
\cs_set_eq:NN \p \guido_coord:nn
\guido_parse_arg:n { ##1 }
#2 { #3 }
\group_end:
}
}
\seq_new:N \l_guido_arg_list_seq
\prop_new:N \l_guido_point_list_prop
\cs_new_protected:Npn \guido_parse_arg:n #1
{
% clear the list of points
\prop_clear:N \l_guido_point_list_prop
% split the arg list at |
\seq_set_split:Nnn \l_guido_arg_list_seq { | } { #1 }
% add each tuple to the property list
\int_step_inline:nnnn { 1 } { 1 } { \seq_count:N \l_guido_arg_list_seq }
{
\__guido_add_point:nx { ##1 } { \seq_item:Nn \l_guido_arg_list_seq { ##1 } }
}
}
\cs_new_protected:Npn \__guido_add_point:nn #1 #2
{
\int_step_inline:nnnn { 1 } { 1 } { \clist_count:n { #2 } }
{
\prop_put:Nnx \l_guido_point_list_prop { ##1 , #1 }
{
\clist_item:nn { #2 } { ##1 }
}
}
}
\cs_generate_variant:Nn \__guido_add_point:nn { nx }
\cs_new:Npn \guido_coord:nn #1 #2
{
\prop_item:Nn \l_guido_point_list_prop { #1,#2 }
}
\cs_new:Npn \guido_x_coord:n #1
{
\guido_coord:nn { #1 } { 1 }
}
\cs_new:Npn \guido_y_coord:n #1
{
\guido_coord:nn { #1 } { 2 }
}
\cs_new:Npn \guido_z_coord:n #1
{
\guido_coord:nn { #1 } { 3 }
}
\ExplSyntaxOff
\newcommand{\guidoforeach}[1]{%
\foreach \n in {0,0.01,0.02,...,0.09}{#1}%
}
\newcommand{\guidoforeachdouble}[1]{%
\foreach \n in {0,0.01,...,0.36}{#1}%
}
\newdrawingcommand{\ltrstroke}[\guidoforeachdouble]{%
\path[
line width=1pt,rounded corners=48pt,line cap=round,draw
]
(\p{1}{1}+\n/1,\p{2}{1}+\n/1)--
(\p{1}{2}+\n/2,\p{2}{2}+\n/2)--
(\p{1}{3}+\n/3,\p{2}{3}-\n/3)--
(\p{1}{4}+\n/4,\p{2}{4}-\n/4);
}
\newdrawingcommand{\ltrinking}{%
\path[
line width=2pt,rounded corners=48pt,draw
](\x{1}+\n/.4,\y{1}+\n/.4)--
(\x{2}+\n/.6,\y{2}+\n/.6)--
(\x{3}+\n/1,\y{3}-\n/1) to
[bend left](\x{4}+\n/4,\y{4}-\n/4)--cycle;
\path[
line width=2pt,rounded corners=48pt
](\x{1},\y{1})--(\x{2},\y{2})--(\x{3},\y{3}) to
[bend left](\x{4},\y{4})--cycle;
}
\begin{document}
\begin{tikzpicture}[bend angle=8];
\ltrinking 0,4,8,4 | 0,4,3,-1 ;
\ltrstroke 0,5 | 0.2 , 5.2 | 3 , 8 | 2,6 ;
\end{tikzpicture}
\end{document}
仍然有可能划定n- 元组用空格表示,但检查正确性会更加困难;在示例中,我表明,使用此语法,空格基本上会被忽略。
当然,输出与以前相同。
答案3
我第一次错过了这个,但尽管这与关于多个参数的问题无关,calligraphy
TikZ 库(起源于“海报”钢笔笔尖风格文字)可以绘制不同粗细的路径。与超棒的hobby
包(来自使用 Metapost 和 TikZ 通过一系列点绘制曲线),可以很轻松地绘制出宽度不一、曲线优美的线条。
\documentclass{article}
%\url{https://tex.stackexchange.com/q/242025/86}
\usepackage{tikz}
\usetikzlibrary{calligraphy,hobby}
\begin{document}
\begin{tikzpicture}[use Hobby shortcut,line width=2pt]
\pen (0,0);
\calligraphy[scale=.02,heavy] (0,0)..(100,-32)..(192,192)..(256,64);
\end{tikzpicture}
\end{document}
答案4
这只是展示了解析元组的另一种公式,它采用了其他答案中提出的一些想法。如果dotuples
像这样调用(命名不当的)宏:
\dotuples{x,y,z}{\n}{1,2,3 | 4,5,6 | 7,8,9 | 10,11,12}
然后\x0
定义为1
,\y0
定义为2
,\z0
定义为3
。这继续遍历所有元组,所以\z3
是12
。最后,宏\n
包含最后一个元组的索引(在本例中为3
)。
第一个参数指定\dotuples
存储元组的变量的名称以及每个元组中预期的元素数量,它可以任意扩展,因此可以说:
\dotuples{a,b,c,d,e,f}{\n}{1,2,3,4,5,6 | 11,12,13,14,15,16}
元组元素中没有巧妙地包含空格(这可能是可取的,也可能不是)。将分隔符更改|
为其他分隔符应该相当简单(如果所需的分隔符是空格,则稍微不那么简单)。
\documentclass[varwidth,border=5pt]{standalone}
\usepackage{pgf,pgffor}
\makeatletter
\def\dotuple#1#2#3{%
\pgfutil@tempcnta=0\relax%
\pgfutil@for\@tmp:={#1}\do{%
\expandafter\edef\csname tuple@var@\the\pgfutil@tempcnta\endcsname{\@tmp}%
\expandafter\@makevar\expandafter{\@tmp}%
\advance\pgfutil@tempcnta by1\relax%
}%
\def\tuple@count@var{#2}%
\pgfutil@tempcnta=0\relax%
\@dotuple#3||%
}
\def\@makevar#1{\expandafter\def\csname#1\endcsname##1{\csname#1@##1\endcsname}}
\def\@dotuple#1|{%
\def\@tmp{#1}%
\ifx\@tmp\pgfutil@empty%
\let\@next=\relax%
\advance\pgfutil@tempcnta by-1\relax%
\expandafter\edef\tuple@count@var{\the\pgfutil@tempcnta}%
\else%
\pgfutil@tempcntb=0\relax%
\pgfutil@for\@tmp:={#1}\do{%
\edef\@tmpvar{\csname tuple@var@\the\pgfutil@tempcntb\endcsname}%
\expandafter\edef\csname\@tmpvar @\the\pgfutil@tempcnta\endcsname{\@tmp}%
\advance\pgfutil@tempcntb by1\relax%
}%
\advance\pgfutil@tempcnta by1\relax%
\let\@next=\@dotuple%
\fi%
\@next}
\begin{document}
\dotuple{x,y,p}{\n}{ 1,2,3 | 4.5,6,7 | 8,9.10,11 | 12.13,14,-15.16}
\ttfamily%
\foreach \i in {0,...,\n}{ x[\i]=\x\i, y[\i]=\y\i, p[\i]=\p\i \par }
\end{document}