对于厚度连续变化的曲线,宏中的参数数量是某个数字的倍数

对于厚度连续变化的曲线,宏中的参数数量是某个数字的倍数

在一些数学图形中,沿路径平滑地改变曲线厚度是很好的。这是为了再现用墨线绘制的手绘数学图形。

曲线厚度变化

\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|一个参数,提取xyp

在 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

我第一次错过了这个,但尽管这与关于多个参数的问题无关,calligraphyTikZ 库(起源于“海报”钢笔笔尖风格文字)可以绘制不同粗细的路径。与超棒的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。这继续遍历所有元组,所以\z312。最后,宏\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}

在此处输入图片描述

相关内容