这是我第一次尝试使用乳胶制作动画图像。
我在 stackexchange 上进行了很多搜索,并了解到:
- 定义数组
\def\array{{}}
...
数组符号{1,...,5}
= {1,2,3,4,5}- 使用相同的代码绘制多幅图像
\foreach \i in {...} { \begin{tikzpicture} ...code... \end{tikzpicture}
- 使用进行计算
\foreach [evaluate = {...computation...}]
- 使用条件 if/else 和
result = cond?v0:v1
(cond true -> result = v0, cond false -> result = v1)。
我的目的是画出两个点在两条线上以不同的“速度”移动(比如a
和b
),以表明如果它们同时从线上的同一点出发,那么它们将在最小公倍数给出的时刻在同一点再次相遇lcm(a, b)
。
在该示例中,“速度”或者更准确地说,点返回其初始位置所需的时间为4
和6
,因此点将在 时刻再次相遇12
。
为了让动画足够流畅,我将第一行分成24
(= 4*6) 个相等的部分,将第二行分成36
(= 6*6) 个相等的部分。因此,初始数组0,...,24
意味着第一个点从左向右移动,23,...,0
从右向左移动,并执行 3 次 (3*4 = 12)。类似地,第二个点从左向右移动0,...,36
,从右向左35,...,0
移动,并执行 2 次 (2*6 = 12)。
我认为代码的这部分可以改进:
- 初始数组的定义,是否可以以紧凑的方式定义它们?例如通过连接数组
{0,...,24}+{23,...,0}+{1,...,24}+...
- 为了能够计算线左侧和点上方的数字,我
fill
在里面写了一个虚拟的foreach
,虚拟的,因为循环只执行一次。是否可以直接在命令中计算数字fill
? - 所需数字的计算太过笨拙
代码的总体结构是好是坏?如何改进?
\documentclass[tikz, 12pt]{standalone}
\usepackage{tikz}
\begin{document}
% define the arrays containing the x axis coords to draw the moving points
\def\four{{0,
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,
23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,
23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,
23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}}
\def\six{{0,
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,
35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,
35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}}
% generate an image for each point (each array contain 145 points)
\foreach \i in {0,...,144}
{
\begin{tikzpicture}
% draw a frame so that size of all images is the same
\draw (-1,-2) rectangle (7,1);
% first line
\draw[gray, thick] (0,0) -- (6,0);
% compute the number on the left
% it is a multiple of 4 obtained by multiplying by 4 the integer part of the quotient of the current cycle index divided by the double of 24
\foreach [evaluate = {\multiple = int(4*floor(\i/48))}] \dummy in {1}
\fill (0,0) node [left]{\multiple};
% draw the point and compute the number above the point
% the number is computed as the integer part of the remainder of the division of the current cycle index divided by the double of 24, divided by 12, and when the remainder is 0 it is added 1
\foreach [evaluate = {\counter = int(int(floor(mod(\i,48)/12))+int(int(\i==0?0:1)*int(4*int(mod(\i,48)==0?1:0))))}] \dummy in {1}
\fill (\four[\i]/4,0) circle (2pt) node [above]{\counter};
% draw a bar on the line where the number above the point changes
\foreach \x in {0,3,6}
\draw (\x,0.1) -- (\x,-0.1);
% second line
\draw[gray, thick] (0,-1) -- (6,-1);
% compute the number on the left
\foreach [evaluate = {\multiple = int(6*floor(\i/72))}] \dummy in {1}
\fill (0,-1) node [left]{\multiple};
% draw the point and compute the number above the point
\foreach [evaluate = {\counter = int(int(floor(mod(\i,72)/12))+int(int(\i==0?0:1)*int(6*int(mod(\i,72)==0?1:0))))}] \dummy in {1}
\fill (\six[\i]/6,-1) circle (2pt) node [above]{\counter};
% draw a bar on the line where the number above the point changes
\foreach \x in {0,2,4,6}
\draw (\x,-1.1) -- (\x,-0.9);
\end{tikzpicture}
}
\end{document}
答案1
关于您的第一个问题,我建议使用expl3
代码来自动生成坐标序列。\four
然后\six
可以使用以下两行来定义您的命令:
\defineCoordsSeq{\four}{0}{24}{3}
\defineCoordsSeq{\six}{0}{36}{2}
我更喜欢将它们重命名为\fourSeq
并\sixSeq
减少覆盖“其他人”的宏的风险;所以,这就是您将在下面的完整示例中看到的内容。
关于第二个问题,您可以使用:
let ... in ...
执行构造路径局部定义的操作。使用,let \n〈number register〉={〈formula〉}
〈公式〉 使用 进行评估。如果您喜欢编写简洁的代码,则\pgfmathparse
可以使用\n1
、\n2
等代替。请参阅\n{multiple}
Let 操作在里面钛钾Z & PGF 手册了解详情。\pgfmathsetmacro{〈macro〉}{〈formula〉}
或\pgfmathtruncatemacro{〈macro〉}{〈formula〉}
。后者将结果存储为不带小数分隔符的整数,这在您在此处的部分或全部用途中都是可取的。无论哪种情况,宏定义都是当前 TeX 组的本地宏定义。
下面的代码展示了这两种技术的示例。\pgfmathtruncatemacro
当你有较长的公式,并且外部int()
包含整个公式时,我会使用它;\pgfmathtruncatemacro
这样我们就可以摆脱这个外部int()
调用。
关于您的第三个问题,我相信您的几个int()
电话可以被删除,但由于我并不真正了解其内部结构pgfmath
,所以我更愿意让其他人来解决这个问题。
这是我的代码:
\documentclass[tikz]{standalone}
\usepackage{xparse}
\usetikzlibrary{calc} % for the let ... in ... operation
\ExplSyntaxOn
% #1: seq variable
% #2: minimum value
% #3: maximum value
% #4: number of back-and-forths
\cs_new_protected:Npn \soundwave_fill_coords_sequence:Nnnn #1#2#3#4
{
\seq_clear:N #1
\int_step_inline:nn {#4}
{
\int_step_inline:nnnn {#2} { 1 } { (#3) - 1 }
{ \seq_put_right:Nn #1 {####1} }
\int_step_inline:nnnn {#3} { -1 } { (#2) + 1 }
{ \seq_put_right:Nn #1 {####1} }
}
\seq_put_right:Nn #1 {#2}
}
\seq_new:N \l__soundwave_tmp_seq
\cs_new_protected:Npn \soundwave_define_coords_seq_as_command:Nnnn #1#2#3#4
{
\soundwave_fill_coords_sequence:Nnnn \l__soundwave_tmp_seq {#2} {#3} {#4}
\edef #1 { { \seq_use:Nn \l__soundwave_tmp_seq { , } } }
}
% #1: control sequence token
% #2: minimum value
% #3: maximum value
% #4: number of back-and-forths
\NewDocumentCommand \defineCoordsSeq { m m m m }
{
\soundwave_define_coords_seq_as_command:Nnnn #1 {#2} {#3} {#4}
}
\ExplSyntaxOff
\defineCoordsSeq{\fourSeq}{0}{24}{3}
\defineCoordsSeq{\sixSeq}{0}{36}{2}
\begin{document}
% Generate an image for each point (each array contain 145 points).
\foreach \i in {0,...,144}
{
\begin{tikzpicture}
% Draw a frame so that size of all images is the same.
\draw (-1,-2) rectangle (7,1);
% First line
\draw[gray, thick] (0,0) -- (6,0);
% Compute the number on the left.
% It is a multiple of 4 obtained by multiplying by 4 the integer part of the
% quotient of the current cycle index divided by the double of 24.
\fill let \n{multiple} = { int(4*floor(\i/48)) }
in (0,0) node [left]{\n{multiple}};
% Draw the point and compute the number above the point.
% The number is computed as the integer part of the remainder of the division
% of the current cycle index divided by the double of 24, divided by 12, and
% when the remainder is 0 it is added 1.
\pgfmathtruncatemacro{\counter}{
int(floor(mod(\i,48)/12)) + int(int(\i==0?0:1)*int(4*int(mod(\i,48)==0?1:0)))
} % this definition of \counter lasts for the duration of the current group
\fill (\fourSeq[\i]/4,0) circle (2pt) node [above]{\counter};
% Draw a bar on the line where the number above the point changes.
\foreach \x in {0,3,6}
\draw (\x,0.1) -- (\x,-0.1);
% Second line
\draw[gray, thick] (0,-1) -- (6,-1);
% Compute the number on the left.
\fill let \n{multiple} = { int(6*floor(\i/72)) }
in (0,-1) node [left]{\n{multiple}};
% Draw the point and compute the number above the point.
\pgfmathtruncatemacro{\counter}{
int(floor(mod(\i,72)/12)) + int(int(\i==0?0:1)*int(6*int(mod(\i,72)==0?1:0)))
}
\fill (\sixSeq[\i]/6,-1) circle (2pt) node [above]{\counter};
% Draw a bar on the line where the number above the point changes.
\foreach \x in {0,2,4,6}
\draw (\x,-1.1) -- (\x,-0.9);
\end{tikzpicture}
}
\end{document}
结果看起来与您的相同,只是我删除了选项12pt
以\documentclass
使代码尽可能接近最小化。