我一直在尝试构建一个宏\parabola{...}
来使用 TikZ 绘制一条经过 3 个坐标的抛物线,但没有成功。
例如
\parabola{A}{B}{C}
A
将绘制插入 (x,y) 坐标 ( )、( B
) 和 ( )的抛物线C
。我还想指定曲线的样式、绘图域等。
我发现的主要问题是我无法弄清楚如何以无量纲形式(给定某个单位)获取给定坐标的值。
答案1
介绍
这是一个老问题,但之前的所有答案都有局限性:主要局限性是所有使用plot
. 和plot
命令都会产生多个三次曲线。但要绘制抛物线,一条二次(三次)曲线就足够了。
一些解释
任何抛物线都可以用二次贝塞尔曲线绘制,因此也可以用三次贝塞尔曲线绘制。
(当且仅当 ,具有控制点的三次曲线A,B,C,D
绘制二次曲线AD=3BC
。)
t(1-t)
上的“标准”抛物线[0,1]
可以用 来绘制\draw (0,0) .. controls (1/3,1/3) and (2/3,1/3) .. (1,0);
。
两点之间的每条抛物线都可以通过从这个“标准”抛物线进行仿射变换获得。利用这一点,我们可以定义一种parabola through
使用单个贝塞尔曲线绘制所需抛物线的样式。此样式可以与to
或一起使用edge
,方式如下(A) to[parabola through={(B)}] (C)
。
代码
的定义parabola through
是:
\makeatletter
\def\pt@get#1#2{
\tikz@scan@one@point\pgfutil@firstofone#2\relax%
\csname pgf@x#1\endcsname=\pgf@x%
\csname pgf@y#1\endcsname=\pgf@y%
}
\tikzset{
parabola through/.style={
to path={{[x={(\pgf@xc,\pgf@yc)}, y=\parabola@y, shift=(\tikztostart)]
-- (0,0) .. controls (1/3,1/3) and (2/3,1/3) .. (1,0) \tikztonodes}--(\tikztotarget)}
},
parabola through/.prefix code={
\pt@get{a}{(\tikztostart)}\pt@get{b}{#1}\pt@get{c}{(\tikztotarget)}%
\advance\pgf@xb by-\pgf@xa\advance\pgf@yb by-\pgf@ya%
\advance\pgf@xc by-\pgf@xa\advance\pgf@yc by-\pgf@ya%
\pgfmathsetmacro\parabola@y{(\pgf@yc-\pgf@xc/\pgf@xb*\pgf@yb)%
/(\pgf@xb-\pgf@xc)*\pgf@xc}%
}
}
\makeatother
注意:我们可以通过使用库来避免\makeatletter
/\makeatother
和 all @
s 。let
calc
我们可以用(A) to[parabola through={(B)}] (C)
:
- 在抛物线存在的每一个情况下,当三个 x 坐标不同时,
- 该点
B
可以位于绘制区域之外, - 这可以是具有位于其上的节点的通用路径的一部分。
示例 1:
\tikz\draw[help lines] (0,0) grid (4,3)
(0,0) edge[parabola through={(3,2)},
red,thick,fill=blue,fill opacity=.21] (4,1);
示例 2(完整 MWE):
\documentclass[tikz,border=7pt]{standalone}
\makeatletter
\def\pt@get#1#2{
\tikz@scan@one@point\pgfutil@firstofone#2\relax%
\csname pgf@x#1\endcsname=\pgf@x%
\csname pgf@y#1\endcsname=\pgf@y%
}
\tikzset{
parabola through/.style={
to path={{[x={(\pgf@xc,\pgf@yc)}, y=\parabola@y, shift=(\tikztostart)]
-- (0,0) .. controls (1/3,1/3) and (2/3,1/3) .. (1,0) \tikztonodes}--(\tikztotarget)}
},
parabola through/.prefix code={
\pt@get{a}{(\tikztostart)}\pt@get{b}{#1}\pt@get{c}{(\tikztotarget)}%
\advance\pgf@xb by-\pgf@xa\advance\pgf@yb by-\pgf@ya%
\advance\pgf@xc by-\pgf@xa\advance\pgf@yc by-\pgf@ya%
\pgfmathsetmacro\parabola@y{(\pgf@yc-\pgf@xc/\pgf@xb*\pgf@yb)%
/(\pgf@xb-\pgf@xc)*\pgf@xc}%
}
}
\makeatother
\begin{document}
\begin{tikzpicture}
\draw[help lines] (-1,-1) grid (3,3);
% variations of the point "through"
\foreach \y in {-1,-.9,...,1}
\draw[green] (-1,1) node[black]{.}
to[parabola through={(0,\y)}] node[black]{.}
node[black,at end]{.} (1,.5);
% variations of a boundary point
\foreach \y in {1.5,1.7,...,3}
\draw[purple] (-1,2) node[black]{.}
to[parabola through={(0,2)}] node[black]{.}
node[black,at end]{.} (1,\y);
% variations of a point "trough" outside the drawn part
\foreach \y in {-1,-0.5,...,3}{
\draw[red,thick] (.5,1) node[black]{.}
to[parabola through={(3,\y)}] node[black]{.}
node[black,at end]{.} (2,1);
\draw[dashed,blue] (.5,1) node[black]{.}
to[parabola through={(2,1)}] node[black]{.}
node[black,at end]{.} (3,\y);
}
\end{tikzpicture}
\end{document}
与内置抛物线操作相比
TikZ 提供了parabola
路径操作。但是设计得不是很好:
- 应该在0和1之间
(0,0) parabola (1,1)
画一条抛物线t^2
。它画了一条接近这条抛物线的三次曲线,但并不完全相同,实际上它画的是(0,0) .. controls (.5,0) and (0.8875,0.775) .. (1,1)
,但确切的曲线是(0,0) .. controls (1/3,0) and (2/3,1/3) .. (1,1)
(不清楚为什么不使用这条曲线), - 与选项一起使用时
bend
,它使用两个三次曲线来近似抛物线,但只需一条就足以绘制精确的抛物线, - 与选项一起使用时
bend=<point>
,如果点选择不好,曲线就不是抛物线。
有一种情况,当弯曲点(极值点)位于起点或终点时,原始抛物线使用起来更简单(即使没有画出一条完整的抛物线):(0,0) parabola (2,4)
比更简单(0,0) to[parabola through={(1,1)}] (2,4)
。
答案2
1)fp 的变体:
\documentclass{article}
\usepackage{tikz,fp}
\FPmessagesfalse
\FPdebugfalse
\makeatletter
\tikzset{%
parabola through/.style={
to path={%
\pgfextra{%
\tikz@scan@one@point\pgfutil@firstofone(\tikztostart)\relax
\FPeval\xa{\pgf@sys@tonumber{\pgf@x}/28.45274}
\FPeval\ya{\pgf@sys@tonumber{\pgf@y}/28.45274}
\tikz@scan@one@point\pgfutil@firstofone#1\relax
\FPeval\xb{\pgf@sys@tonumber{\pgf@x}/28.45274}
\FPeval\yb{\pgf@sys@tonumber{\pgf@y}/28.45274}
\tikz@scan@one@point\pgfutil@firstofone(\tikztotarget)\relax
\FPeval\xc{\pgf@sys@tonumber{\pgf@x}/28.45274}
\FPeval\yc{\pgf@sys@tonumber{\pgf@y}/28.45274}
\FPeval\pb@a{(\ya*(\xb-\xc)+\yb*(\xc-\xa)+\yc*(\xa-\xb))/%
((\xa-\xb)*(\xa-\xc)*(\xb-\xc))}
\FPeval\pb@b{(\ya*(\xc+\xb)*(\xc-\xb)+\yb*(\xa+\xc)*(\xa-\xc)+\yc*(\xb+\xa)*(\xb-\xa))/((\xa-\xb)*(\xa-\xc)*(\xb-\xc))}
\FPeval\pb@c{(\ya*\xb*\xc*(\xb-\xc)+\yb*\xa*\xc*(\xc-\xa)+\yc*\xa*\xb*(\xa-\xb))/((\xa-\xb)*(\xa-\xc)*(\xb-\xc))}
\draw plot[domain=\xa:\xc] (\x,{\pb@a*(\x*\x)+\pb@b*\x+\pb@c}) ;
}(\tikztotarget)
}
}
}
\makeatother
\begin{document}
\begin{tikzpicture}
\draw [help lines] (-3,-1) grid (7,4);
\draw (-3,0) to[parabola through={(-2,2)}]%
(0,-1) to[parabola through={(2,4)}] (4,0) to[parabola through={(5,3)}] (7,0);
\end{tikzpicture}
\end{document}
2)来自 maeshtro 的 gnuplot 答案
\documentclass{article}
\usepackage{tikz}
\makeatletter
\tikzset{%
parabola through/.style={
to path={%
\pgfextra{%
\tikz@scan@one@point\pgfutil@firstofone(\tikztostart)\relax
\edef\xa{\pgf@sys@tonumber{\pgf@x}}
\edef\ya{\pgf@sys@tonumber{\pgf@y}}
\tikz@scan@one@point\pgfutil@firstofone#1\relax
\edef\xb{\pgf@sys@tonumber{\pgf@x}}
\edef\yb{\pgf@sys@tonumber{\pgf@y}}
\tikz@scan@one@point\pgfutil@firstofone(\tikztotarget)\relax
\edef\xc{\pgf@sys@tonumber{\pgf@x}}
\edef\yc{\pgf@sys@tonumber{\pgf@y}}
\draw plot[domain=\xa/28.45274:\xc/28.45274] function{
\ya/28.45274*((x*28.45274-\xb)*(x*28.45274-\xc))/((\xa-\xb)*(\xa-\xc))+
\yb/28.45274*((x*28.45274-\xa)*(x*28.45274-\xc))/((\xb-\xa)*(\xb-\xc))+
\yc/28.45274*((x*28.45274-\xa)*(x*28.45274-\xb))/((\xc-\xa)*(\xc-\xb))
};
}(\tikztotarget)
}
}
}
\makeatother
\begin{document}
\begin{tikzpicture}
\draw [help lines] (-3,-1) grid (7,4);
\draw (-3,0) to[parabola through={(-2,2)}] (0,-1) to[parabola through={(2,4)}] (4,0) to[parabola through={(5,3)}] (7,0);
\end{tikzpicture}
\end{document}
答案3
这是一个解决方案。它本质上解决了从二次插值获得的线性方程。但需要注意的是,它远非完美。特别是由于 tikz 计算限制,对可能的点有很强的限制:计算中的数字必须足够小。这显然不是 tikz 的用途。使用其他方法获取系数会更好(sagetex 或 asymptote 或其他)。
let
至少它是在 tikz 路径中使用的一个好例子。
我希望计算足够清楚。系数 A、B 和 C 是二次多项式 Ax^2 + Bx + C 的系数。
必须按照 x 坐标的升序输入点,以确保代码正常工作。
代码如下
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}
\begin{document}
\begin{tikzpicture}
\coordinate (1) at (0.1,0.2);
\coordinate (2) at (0.2,0.7);
\coordinate (3) at (0.4,-0.3);
\draw let \p1 = (1),
\p2 = (2),
\p3 = (3),
\n{denom} = {(\x1 - \x2)*(\x1 - \x3)*(\x2-\x3)},
\n{A} = {(\x3*(\y2-\y1) + \x2*(\y1-\y3) + \x1*(\y3-\y2))/\n{denom}},
\n{B} = {(\x3*\x3*(\y1-\y2) + \x2*\x2*(\y3-\y1)+\x1*\x1*(\y2-\y3))/\n{denom}},
\n{C} = {(\x2*\x3*(\x2-\x3)*\y1 + \x3*\x1*(\x3-\x1)*\y2 + \x1*\x2*(\x1-\x2)*\y3)/\n{denom}} in
plot[domain=\x1:\x3] (\x,{\n{A}*\x*\x+\n{B}*\x + \n{C}});
\end{tikzpicture}
\end{document}
答案4
你可以依赖 来\pgfmathparse
始终返回 中的任何给定长度pt
。看一下输出:
\pgfmathparse{12cm+1pt}
\pgfmathresult
\pgfmathparse{1pt}
\pgfmathresult
输出结果为:
342.43306
1.0
有了它,你几乎可以通过转移到(现在无量纲的)单位来完成所有的计算pt
。然而,你应该对非常大的数字保持警惕。
为了避免这种情况,您可以依赖fpu
单位pgf
。
% Preamble
\usepgflibrary{fpu}
\begin{document}
\pgfkeys{/pgf/fpu,/pgf/fpu/output format=fixed}
\pgfmathparse{12cm+1pt}
\pgfmathresult
\pgfmathparse{1pt}
\pgfmathresult
342.43306000000000
1.0000000000
然后您就可以访问始终位于一个特定单元中的数字!