TikZ 中的自定义形状

TikZ 中的自定义形状

我正在尝试按照手册第 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}

结果:

在此处输入图片描述

相关内容