我正在尝试按照手册第 75.5 节的说明在 TikZ 中定义一个新形状。阅读问题在 LaTeX 中绘制机械系统,我开始认为 TikZ 中确实缺少一个“机械”库,但它不应该缺少,因为它就像电路或图表的库一样有用。所以我开始重现机械约束的符号:
(忘记中心的垂直虚线)目的是重新创建一组用于绘制机械系统和结构的符号。这就是我所做的:
\documentclass[a4paper]{article}
\usepackage{pgfplots}
\makeatletter
\pgfdeclareshape{carr}{%
\inheritsavedanchors[from=circle]
\inheritanchor[from=circle]{center}
\inheritanchor[from=circle]{north}
\inheritanchor[from=circle]{south}
\inheritanchor[from=circle]{east}
\inheritanchor[from=circle]{west}
\backgroundpath{%
% center
\centerpoint \pgf@xa=\pgf@x \pgf@ya=\pgf@y
% west triangle corner
\radius \pgf@yb=-5\pgf@y \centerpoint \advance\pgf@yb by\pgf@y
\pgf@xb=-2\pgf@x
% east triangle corner
\radius \pgf@yc=-5\pgf@y \centerpoint \advance\pgf@yc by\pgf@y
\pgf@xc=4\pgf@x
% draw triangle..
\pgfpathmoveto{\pgfpoint{\pgf@xa}{\pgf@ya}}%
\pgfpathlineto{\pgfpoint{\pgf@xb}{\pgf@yb}}%
\pgfpathlineto{\pgfpoint{\pgf@xc}{\pgf@yc}}%
\pgfpathclose
% central circle (from pgflibraryshapes.geometric.code.tex)..
\pgf@process{\radius}
\pgfutil@tempdima=\pgf@x%
\pgfutil@tempdimb=\pgf@y%
\pgfmathsetlength{\pgf@xb}{\pgfkeysvalueof{/pgf/outer xsep}}%
\pgfmathsetlength{\pgf@yb}{\pgfkeysvalueof{/pgf/outer ysep}}%
\advance\pgfutil@tempdima by-\pgf@xb%
\advance\pgfutil@tempdimb by-\pgf@yb%
\pgfpathellipse{\centerpoint}{\pgfqpoint{\pgfutil@tempdimb}{0pt}}{\pgfqpoint{0pt}{\pgfutil@tempdimb}}%
% and lower circles
\pgf@xa=1pt % the radius:should be a parameter
\pgfutil@tempdima=\pgf@xb \advance\pgfutil@tempdima by\pgf@xa
\pgfutil@tempdimb=\pgf@yb \advance\pgfutil@tempdimb by-\pgf@xa
\pgfpathcircle{\pgfpoint{\pgfutil@tempdima}{\pgfutil@tempdimb}}{\pgf@xa}
\pgfutil@tempdima=\pgf@xb \advance\pgfutil@tempdima by-\pgf@xa
\pgfutil@tempdimb=\pgf@yb \advance\pgfutil@tempdimb by-\pgf@xa
\pgfpathcircle{\pgfpoint{\pgfutil@tempdima}{\pgfutil@tempdimb}}{\pgf@xa}
}}
\makeatother
\begin{document}
\begin{tikzpicture}
\node[shape=carr,draw] at (3,0) {a};
\node[shape=carr,draw] at (5,0) {}; % missing triangle
\draw[shape=carr] (7,0); % nothing shown
\end{tikzpicture}
\end{document}
查看其他库的代码,这似乎是正确的处理方式,但如果我错了,请纠正我。从生成的 pdf 中可以看出,图中的两个下圆位置错位很多,我不明白为什么。用于绘制上圆的代码取自 tikz 中椭圆的定义。此外,我不知道为什么,如果我在节点中使用空标签,矩形会丢失。最后一行代码(\draw[shape=carr....
)只是一个实验。我希望能够编写这样的东西并在 pdf 中看到重现的形状,但我不知道这是否可能。
如何更改代码以实现所需的结果?你认为这只是浪费时间吗?欢迎就任何方向提出建议
答案1
声明形状是该库最有用的功能之一tikz/pgf
。它可以通过无限选择进行扩展,这确实是一个好主意。
形状的问题在于,要让其工作起来,需要经历许多困难。这是因为需要低级编码,而变量的良好记录本质上就是问题所在。如果不习惯低级变量重新分配,这可能会变得很麻烦,甚至是一项如果不编写比基本层提供的更多代码就无法完成的任务。
这也是你的代码的问题。
注释你的代码
\pgfdeclareshape{carr}{%
\inheritsavedanchors[from=circle]
\inheritanchor[from=circle]{center}
\inheritanchor[from=circle]{north}
\inheritanchor[from=circle]{south}
\inheritanchor[from=circle]{east}
\inheritanchor[from=circle]{west}
这些都很好,保留它们就行,你可能会发现你的东、西等位置不正确。因此你应该添加自己的。
\backgroundpath{%
% center
\centerpoint
\pgf@xa=\pgf@x
\pgf@ya=\pgf@y
这也很好,你获得了中心坐标。但是,只有当节点中有文本时,中心坐标才有值。它们定义为:
\pgf@x=.5\wd\pgfnodeparttextbox
\pgf@y=.5\ht\pgfnodeparttextbox
\advance\pgf@y by-.5\dp\pgfnodeparttextbox
因此,如果没有文本,则它们都是0pt
。这告诉您为什么如果没有提供节点文本则不显示。
% west triangle corner
\radius
\pgf@yb=-5\pgf@y
\centerpoint
\advance\pgf@yb by\pgf@y
\pgf@xb=-2\pgf@x
您的问题是这\radius
实际上是一个维度。您不会以任何方式保存此值。您需要将其用作\radius
常规维度:
\pgf@x=\radius
这意味着,无论您保存\pgf@yb
什么,您都不知道它是什么(或者实际上它来自\pgf@y
哪里\centerpoint
,但那是另一回事)。
% east triangle corner
\radius
\pgf@yc=-5\pgf@y
\centerpoint
\advance\pgf@yc by\pgf@y
\pgf@xc=4\pgf@x
同样的问题。这不管用。
% draw triangle..
\pgfpathmoveto{\pgfpoint{\pgf@xa}{\pgf@ya}}%
\pgfpathlineto{\pgfpoint{\pgf@xb}{\pgf@yb}}%
\pgfpathlineto{\pgfpoint{\pgf@xc}{\pgf@yc}}%
\pgfpathclose
这可以
% central circle (from pgflibraryshapes.geometric.code.tex)..
\pgf@process{\radius}
这是另一个错误。让我们来看看是什么\pgf@process
原因造成的。
让我们考虑以下函数:
\def\pgfcalc{\pgf@xb=2pt\pgf@x=\pgf@xb}
\pgfcalc
实际上会定义两者\pgf@xb
和 \pgf@x
。在很多情况下,你只对最终结果感兴趣,即\pgf@x
和\pgf@y
。在这种情况下,人们将\pgfcalc
通过 来调用\pgf@process
。
\pgf@xb=1pt
\pgfcalc
% Here \pgf@xb and \pgf@x are set by \pgfcalc
% So \pgf@xb and \pgf@x are both 2pt
\pgf@xb=1pt
\pgf@process{\pgfcalc}
% Here ONLY \pgf@x is set. \pgf@xb is still 1pt
这意味着您只能调用\pgf@process
宏,而不能调用维度(按原样\radius
)。您引用的代码已\radius
定义为 ,这是一个宏。如果要更改 和 以外的任何内容,您anchor
可以在 上执行此操作。\centerpoint
\centerpoint
\pgf@x
\pgf@y
有关目的的更多信息,请参阅手册。
让我们继续...
\pgfutil@tempdima=\pgf@x%
\pgfutil@tempdimb=\pgf@y%
\pgfmathsetlength{\pgf@xb}{\pgfkeysvalueof{/pgf/outer xsep}}%
\pgfmathsetlength{\pgf@yb}{\pgfkeysvalueof{/pgf/outer ysep}}%
\advance\pgfutil@tempdima by-\pgf@xb%
\advance\pgfutil@tempdimb by-\pgf@yb%
\pgfpathellipse{\centerpoint}{\pgfqpoint{\pgfutil@tempdimb}{0pt}}{\pgfqpoint{0pt}{\pgfutil@tempdimb}}%
% and lower circles
\pgf@xa=1pt % the radius:should be a parameter
\pgfutil@tempdima=\pgf@xb \advance\pgfutil@tempdima by\pgf@xa
\pgfutil@tempdimb=\pgf@yb \advance\pgfutil@tempdimb by-\pgf@xa
\pgfpathcircle{\pgfpoint{\pgfutil@tempdima}{\pgfutil@tempdimb}}{\pgf@xa}
\pgfutil@tempdima=\pgf@xb \advance\pgfutil@tempdima by-\pgf@xa
\pgfutil@tempdimb=\pgf@yb \advance\pgfutil@tempdimb by-\pgf@xa
\pgfpathcircle{\pgfpoint{\pgfutil@tempdima}{\pgfutil@tempdimb}}{\pgf@xa}
}}
最后这部分其实还可以,但是读起来很麻烦,而且很容易出错。我会对它进行更多的划分。
快速解决方案
此解决方案已制作完成,以便您可以更好地使用它。您需要微调和后期处理许多选项。
由您来完成顶部的圆圈等等。
\documentclass{article}
\usepackage{tikz}
\makeatletter
\pgfdeclareshape{carr}{%
\inheritsavedanchors[from=circle]
\inheritanchor[from=circle]{center}
\inheritanchor[from=circle]{north}
\inheritanchor[from=circle]{south}
\inheritanchor[from=circle]{east}
\inheritanchor[from=circle]{west}
\backgroundpath{%
% Save radius to x
\pgf@x=\radius
% Radius is also containing the "minimum width" and "minimum height"
% This ensures that even with no text the shape will be drawn.
% Unless of course that min are set to 0pt
% So no need to check for that
% Save radius
\pgfutil@tempdima=\pgf@x%
% west triangle corner "b"
\pgf@xb=-3\pgf@x%
\pgf@yb=-4\pgf@x%
% east triangle corner "c"
\pgf@xc= 3\pgf@x%
\pgf@yc=-4\pgf@x%
% If text is present shift shape to center
% You need to shift more, but to get the idea
\centerpoint
\advance\pgf@xb by\pgf@x
\advance\pgf@yb by\pgf@y
\advance\pgf@xc by\pgf@x
\advance\pgf@yc by\pgf@y
% Save centerpoint in "a" (top triangle point)
\pgf@xa=\pgf@x
\pgf@ya=\pgf@y
% Below are good for debugging purposes.
%\message{^^JTop : \the\pgf@xa,\the\pgf@ya}
%\message{^^JWest: \the\pgf@xb,\the\pgf@yb}
%\message{^^JEast: \the\pgf@xc,\the\pgf@yc}
%\message{^^JCent: \the\pgf@x,\the\pgf@y}
% draw triangle..
\pgfpathmoveto{\pgfpoint{\pgf@xa}{\pgf@ya}}%
\pgfpathlineto{\pgfpoint{\pgf@xb}{\pgf@yb}}%
\pgfpathlineto{\pgfpoint{\pgf@xc}{\pgf@yc}}%
\pgfpathclose
% The radius of the small circles
% Read in from option TODO
\pgfutil@tempdimb=3pt
% Move top triangle to head circle
\advance\pgf@ya by.25\pgfutil@tempdimb
% Move west triangle corner to west circle center
\advance\pgf@xb by 1.5\pgfutil@tempdima
\advance\pgf@yb by -\pgfutil@tempdimb
% For handling line thickness if you wish "edge touch" and not "overlap"
%\advance\pgf@yb by -.5\pgflinewidth
% Move east triangle corner to east circle center
\advance\pgf@xc by-1.5\pgfutil@tempdima
\advance\pgf@yc by -\pgfutil@tempdimb
% For handling line thickness if you wish "edge touch" and not "overlap"
%\advance\pgf@yc by -.5\pgflinewidth
% This saves underlying "stuff" when you have the explicit `\pgfqpoint` and is thus a little faster
\edef\pgf@marshal{%
\noexpand\pgfpathcircle{%
\noexpand\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}
{\the\pgfutil@tempdimb}%
\noexpand\pgfpathcircle{%
\noexpand\pgfqpoint{\the\pgf@xb}{\the\pgf@yb}}
{\the\pgfutil@tempdimb}%
\noexpand\pgfpathcircle{%
\noexpand\pgfqpoint{\the\pgf@xc}{\the\pgf@yc}}
{\the\pgfutil@tempdimb}%
}\pgf@marshal
}}
\makeatother
\begin{document}
\begin{tikzpicture}
\node[shape=carr,draw] at (3,0) {a};
\node[shape=carr,draw] at (5,0) {}; % missing triangle (not anymore)
% Your \draw example will never work! shapes are nodes, you need a node to assign the shape!
\end{tikzpicture}
\end{document}
最终结果如下:
祝你好运战胜这些形状!:)
答案2
从 @zeroth 的回答开始,我添加了一些有用的东西,所以我分享了可能是“最终结果”的内容。我会评论一下差异:
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{patterns}
\tikzstyle{ground}=[fill,pattern=north east lines,draw=none,%
minimum width=1cm,minimum height=0.3cm]
这个 tikzstyle 只是为了将来的测试(见示例末尾)
\makeatletter
\pgfdeclareshape{carr}{%
\inheritsavedanchors[from=circle]
\saveddimen{\halfradius}{%
% getting half the value of \radius (the last saved "anchor")
\pgf@x=.5\pgf@x}
在这里,我创建了一个新维度,它始终是最后一个“已保存维度”的一半,在本例中\radius
(参见在 \pgfdeclareshape 命令中使用已保存的维度。这只是因为圆圈太大(个人而言),然后我想减小它们的标准尺寸。也许最好复制此代码之前的定义,\radius
以避免由于圆圈原始代码的更改而导致问题。
% redefining anchors starting from "circle" because of the different radius
% of the circles (it is equal to \radius/2, i.e. \halfradius)
\anchorborder{
\pgf@xa=\pgf@x%
\pgf@ya=\pgf@y%
\edef\pgf@marshal{%
\noexpand\pgfpointborderellipse
{\noexpand\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}
{\noexpand\pgfqpoint{\halfradius}{\halfradius}}% <- edited here
}%
\pgf@marshal%
\pgf@xa=\pgf@x%
\pgf@ya=\pgf@y%
\centerpoint%
\advance\pgf@x by\pgf@xa%
\advance\pgf@y by\pgf@ya%
}
% Taking some useful anchors from "circle" and defining others of them
\inheritanchor[from=circle]{center}
\inheritanchor[from=circle]{mid}
\inheritanchor[from=circle]{base}
\anchor{north}{\centerpoint\advance\pgf@y by\halfradius}
\anchor{south}{\centerpoint\advance\pgf@y by-\halfradius}
\anchor{west}{\centerpoint\advance\pgf@x by-\halfradius}
\anchor{east}{\centerpoint\advance\pgf@x by\halfradius}
\anchor{mid west}{\centerpoint\advance\pgf@x by-\halfradius\pgfmathsetlength\pgf@y{.5ex}}
\anchor{mid east}{\centerpoint\advance\pgf@x by\halfradius\pgfmathsetlength\pgf@y{.5ex}}
\anchor{base west}{\centerpoint\advance\pgf@x by-\halfradius\pgf@y=0pt}
\anchor{base east}{\centerpoint\advance\pgf@x by\halfradius\pgf@y=0pt}
\anchor{north west}{
\centerpoint
\pgf@xa=\halfradius
\advance\pgf@x by-0.707107\pgf@xa
\advance\pgf@y by0.707107\pgf@xa
}
\anchor{south west}{
\centerpoint
\pgf@xa=\halfradius
\advance\pgf@x by-0.707107\pgf@xa
\advance\pgf@y by-0.707107\pgf@xa
}
\anchor{north east}{
\centerpoint
\pgf@xa=\halfradius
\advance\pgf@x by0.707107\pgf@xa
\advance\pgf@y by0.707107\pgf@xa
}
\anchor{south east}{
\centerpoint
\pgf@xa=\halfradius
\advance\pgf@x by0.707107\pgf@xa
\advance\pgf@y by-0.707107\pgf@xa
}
\anchor{bottom}{% added on the baseline of the shape
\centerpoint
\pgf@xa=\radius
\advance\pgf@y by-4\pgf@xa
\advance\pgf@y by-.5\pgflinewidth
}
由于和之间的差异\radius
,\halfradius
我不得不重新定义一些锚点和\anchorborder
。这样,当您想要从形状的节点绘制一条线时carr
,该线将从上圆的边界开始。此外,我在形状的基线处定义了一个新的锚点,用于放置地面(见图)。
\backgroundpath{%
% Save radius to x
\pgf@x=\radius
% Radius is also containing the "minimum width" and "minimum height"
% This ensures that even with no text the shape will be drawn.
% Unless of course that min are set to 0pt
% So no need to check for that
% Save radius
\pgfutil@tempdima=\pgf@x% maybe useless now
% west triangle corner "b"
\pgf@xb=-2\pgf@x%
\pgf@yb=-3\pgf@x%
% east triangle corner "c"
\pgf@xc= 2\pgf@x%
\pgf@yc=-3\pgf@x%
% If text is present shift shape to center
% You need to shift more, but to get the idea
\centerpoint
\advance\pgf@xb by\pgf@x
\advance\pgf@yb by\pgf@y
\advance\pgf@xc by\pgf@x
\advance\pgf@yc by\pgf@y
% Save centerpoint in "a" (top triangle point)
\pgf@xa=\pgf@x
\pgf@ya=\pgf@y
% Below are good for debugging purposes.
%\message{^^JTop : \the\pgf@xa,\the\pgf@ya}
%\message{^^JWest: \the\pgf@xb,\the\pgf@yb}
%\message{^^JEast: \the\pgf@xc,\the\pgf@yc}
%\message{^^JCent: \the\pgf@x,\the\pgf@y}
%\message{^^JR: \the\pgfutil@tempdima}
这完全一样
% draw triangle (considering upper circle)..
% 0.5*radius*cos(alpha) and 0.5*radius*sin(alpha) -> see \pgfutil@tempdimb
% (0.5547 0.832)
% starting from upper left edge
\pgfutil@tempdima=\halfradius
\advance\pgf@xa by-0.5547\pgfutil@tempdima%
\advance\pgf@ya by-0.832\pgfutil@tempdima%
\pgfpathmoveto{\pgfpoint{\pgf@xa}{\pgf@ya}}%
\pgfpathlineto{\pgfpoint{\pgf@xb}{\pgf@yb}}%
\pgfpathlineto{\pgfpoint{\pgf@xc}{\pgf@yc}}%
% changing to upper right edge...
\advance\pgf@xa by1.1094\pgfutil@tempdima%
\pgfpathlineto{\pgfpoint{\pgf@xa}{\pgf@ya}}%
% ...and back to upper left to close the path
\advance\pgf@xa by-1.1094\pgfutil@tempdima%
\pgfpathmoveto{\pgfpoint{\pgf@xa}{\pgf@ya}}%
\pgfpathclose%
为了删除上圆中三角形的一小部分,我开始从圆的边界绘制路径。为此,我根据需要修改了\pgf@xa
和的值,\pgf@ya
因为使用不同的长度(\pgf@xd
例如)会导致undefined control sequence
错误。
% Restore centerpoint in "a" (top triangle point) and the value of
% \pgfutil@tempdima
\advance\pgf@xa by0.5547\pgfutil@tempdima%
\advance\pgf@ya by0.832\pgfutil@tempdima%
\pgfutil@tempdima=\radius
% The radius of the small circles
% Read in from option TODO
\pgfutil@tempdimb=\halfradius% was 3pt
这里小圆圈的半径设置为\halfradius
。这样,标签或命令的存在就会\huge
连贯地改变整个形状的尺寸
% % Move top triangle to head circle
% \advance\pgf@ya by.25\pgfutil@tempdimb
在我看来,当关闭此功能时,标签最好位于上圆圈的中心,但我可能错了。
% Move west triangle corner to west circle center
\advance\pgf@xb by 1\pgfutil@tempdima
\advance\pgf@yb by -\pgfutil@tempdimb
% For handling line thickness if you wish "edge touch" and not "overlap"
%\advance\pgf@yb by -.5\pgflinewidth
% Move east triangle corner to east circle center
\advance\pgf@xc by-1\pgfutil@tempdima
\advance\pgf@yc by -\pgfutil@tempdimb
%% For handling line thickness if you wish "edge touch" and not "overlap"
%\advance\pgf@yc by -.5\pgflinewidth
% This saves underlying "stuff" when you have the explicit `\pgfqpoint` and is thus a little faster
\edef\pgf@marshal{%
\noexpand\pgfpathcircle{%
\noexpand\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}
{\the\pgfutil@tempdimb}%
\noexpand\pgfpathcircle{%
\noexpand\pgfqpoint{\the\pgf@xb}{\the\pgf@yb}}
{\the\pgfutil@tempdimb}%
\noexpand\pgfpathcircle{%
\noexpand\pgfqpoint{\the\pgf@xc}{\the\pgf@yc}}
{\the\pgfutil@tempdimb}%
}\pgf@marshal
}}
\makeatother
\begin{document}
\begin{tikzpicture}
\draw (0,0) -- (4,0);
\node[shape=carr,draw] (a) at (1,0) {O}; % the shape changes as a whole
\node[shape=carr,draw] (b) at (3,0) {}; % standard
{\Huge \node[shape=carr,draw] (c) at (5,0) {};} % good
\end{tikzpicture}\\[1cm]
\begin{tikzpicture}
\coordinate (a) at (0,0);
\coordinate (b) at (2,2);
\coordinate (c) at (5,2);
\node[draw,carr,rotate=22] (A) at (a){};
\node[draw,carr,fill=red] (B) at (b){}; % fill works (obvious?)
\node[draw,carr,pattern=fivepointed stars] (C) at (c){}; % pattern works
\node[ground,anchor=north] (g1) at (C.bottom){}; % the new anchor is ok
\draw (g1.north west) -- (g1.north east);
\draw[thick] (A) -- (B) -- (C); % good connections (\halfradius)
\draw[thick] (A.north) |- (B.west);
\end{tikzpicture}
\end{document}
结果: