2

How can I create an own TikZ node with several shapes like the final state in an UML diagram?

\tikzstyle{umlfinal} = [...]

Within a tikz picture environment, it's pretty simple:

\documentclass{minimal}
\usepackage{tikz}
\begin{document}

\begin{tikzpicture}[node distance=1.5cm]
  \fill[black] (0,0) circle (7mm);
  \draw[black] (0,0) circle (10mm);
\end{tikzpicture}

\end{document}

But how can I create a node of these two circles?

1
  • I didn't see it until now in your question, but \tikzstyle is deprecated, you should use \tikzset and the .style handler to define new styles instead.
    – Skillmon
    Oct 28, 2023 at 19:03

1 Answer 1

4

The easiest way might be to define your shape as a pic:

\documentclass[border=3.14,tikz]{standalone}
\usepackage{tikz}

\tikzset
  {
     UML/end/.pic={%
      \draw[black]
        % reduce code duplication, we could also put the \fill here
        pic{UML/aux/end}
        circle[radius=\pgfkeysvalueof{/tikz/UML/end/radius}];
    }
    ,UML/end/radius/.initial = 1cm
    ,UML/end/.style={%
      append after command={pic{UML/aux/end}},
      % we use the node shape for the outer circle, that way you can also draw
      % connections and use the nodes anchors
      draw, circle, minimum width=2*\pgfkeysvalueof{/tikz/UML/end/radius},
      inner sep=0pt,
    }
    ,UML/aux/end/.pic={%
      \fill[black] circle[radius=.7*\pgfkeysvalueof{/tikz/UML/end/radius}];
    }
  }

\begin{document}

\begin{tikzpicture}[node distance=1.5cm]
  % works
  \node[UML/end]{};

  % bug
  \node at (0, -3)[UML/end]{};

  % works
  \draw (2, 0) pic{UML/end}
        (4, 0) node[UML/end]{};
\end{tikzpicture}

\end{document}

enter image description here


Using label instead of append after command and setting up the second node in the right way, we can get a simple version that works with positioning and at:

\documentclass[border=3.14,tikz]{standalone}
\usepackage{tikz}
\usetikzlibrary{positioning}

\tikzset
  {
     UML/end/radius/.initial = 1cm
    ,UML/end/.style={
      draw, circle, minimum width=2*\pgfkeysvalueof{/tikz/UML/end/radius},
      label={
        [fill, circle, minimum width=1.4*\pgfkeysvalueof{/tikz/UML/end/radius}]%
        center:%
      }
    }
  }

\begin{document}

\begin{tikzpicture}[node distance=1.5cm]
  \node[UML/end](foo){};
  \node[UML/end, below=of foo]{};
  \node[UML/end] at (4, 0) {};
\end{tikzpicture}

\end{document}

The following defines a new shape at the pgf-layer named UMLend and uses it to create a node-style that can be used with the at syntax as well as the methods of the positioning library.

The code is mostly a copy of the circ-shape, only two lines are added namely:

    \pgfpathcircle{\centerpoint}{.7\pgfutil@tempdima}%
    \pgfusepath{fill}%

I'm not entirely sure whether this is completely safe, but I guess it should be.

The complete code:

\documentclass[border=3.14,tikz]{standalone}
\usepackage{tikz}
\usetikzlibrary{positioning}

\tikzset{UML/end/.style={UMLend, draw, minimum width=2cm}}

\makeatletter
% This is a copy of PGF's definition of circle
\pgfdeclareshape{UMLend}
%
% Draws a circle around the text
%
{%
  \savedanchor\centerpoint{%
    \pgf@x=.5\wd\pgfnodeparttextbox%
    \pgf@y=.5\ht\pgfnodeparttextbox%
    \advance\pgf@y by-.5\dp\pgfnodeparttextbox%
  }%

  \saveddimen\radius{%
    %
    % Calculate ``height radius''
    %
    \pgf@ya=.5\ht\pgfnodeparttextbox%
    \advance\pgf@ya by.5\dp\pgfnodeparttextbox%
    \pgfmathsetlength\pgf@yb{\pgfkeysvalueof{/pgf/inner ysep}}%
    \advance\pgf@ya by\pgf@yb%
    %
    % Calculate ``width radius''
    %
    \pgf@xa=.5\wd\pgfnodeparttextbox%
    \pgfmathsetlength\pgf@xb{\pgfkeysvalueof{/pgf/inner xsep}}%
    \advance\pgf@xa by\pgf@xb%
    %
    % Calculate length of radius vector:
    %
    \pgf@process{\pgfpointnormalised{\pgfqpoint{\pgf@xa}{\pgf@ya}}}%
    \ifdim\pgf@x>\pgf@y%
        \c@pgf@counta=\pgf@x%
        \ifnum\c@pgf@counta=0\relax%
        \else%
          \divide\c@pgf@counta by 255\relax%
          \pgf@xa=16\pgf@xa\relax%
          \divide\pgf@xa by\c@pgf@counta%
          \pgf@xa=16\pgf@xa\relax%
        \fi%
      \else%
        \c@pgf@counta=\pgf@y%
        \ifnum\c@pgf@counta=0\relax%
        \else%
          \divide\c@pgf@counta by 255\relax%
          \pgf@ya=16\pgf@ya\relax%
          \divide\pgf@ya by\c@pgf@counta%
          \pgf@xa=16\pgf@ya\relax%
        \fi%
    \fi%
    \pgf@x=\pgf@xa%
    %
    % If necessary, adjust radius so that the size requirements are
    % met:
    %
    \pgfmathsetlength{\pgf@xb}{\pgfkeysvalueof{/pgf/minimum width}}%
    \pgfmathsetlength{\pgf@yb}{\pgfkeysvalueof{/pgf/minimum height}}%
    \ifdim\pgf@x<.5\pgf@xb%
        \pgf@x=.5\pgf@xb%
    \fi%
    \ifdim\pgf@x<.5\pgf@yb%
        \pgf@x=.5\pgf@yb%
    \fi%
    %
    % Now, add larger of outer separations.
    %
    \pgfmathsetlength{\pgf@xb}{\pgfkeysvalueof{/pgf/outer xsep}}%
    \pgfmathsetlength{\pgf@yb}{\pgfkeysvalueof{/pgf/outer ysep}}%
    \ifdim\pgf@xb<\pgf@yb%
      \advance\pgf@x by\pgf@yb%
    \else%
      \advance\pgf@x by\pgf@xb%
    \fi%
  }%

  %
  % Anchors
  %
  \anchor{center}{\centerpoint}%
  \anchor{mid}{\centerpoint\pgfmathsetlength\pgf@y{.5ex}}%
  \anchor{base}{\centerpoint\pgf@y=0pt}%
  \anchor{north}{\centerpoint\advance\pgf@y by\radius}%
  \anchor{south}{\centerpoint\advance\pgf@y by-\radius}%
  \anchor{west}{\centerpoint\advance\pgf@x by-\radius}%
  \anchor{east}{\centerpoint\advance\pgf@x by\radius}%
  \anchor{mid west}{\centerpoint\advance\pgf@x by-\radius\pgfmathsetlength\pgf@y{.5ex}}%
  \anchor{mid east}{\centerpoint\advance\pgf@x by\radius\pgfmathsetlength\pgf@y{.5ex}}%
  \anchor{base west}{\centerpoint\advance\pgf@x by-\radius\pgf@y=0pt}%
  \anchor{base east}{\centerpoint\advance\pgf@x by\radius\pgf@y=0pt}%
  \anchor{north west}{
    \centerpoint
    \pgf@xa=\radius
    \advance\pgf@x by-0.707107\pgf@xa
    \advance\pgf@y by0.707107\pgf@xa
  }%
  \anchor{south west}{
    \centerpoint
    \pgf@xa=\radius
    \advance\pgf@x by-0.707107\pgf@xa
    \advance\pgf@y by-0.707107\pgf@xa
  }%
  \anchor{north east}{
    \centerpoint
    \pgf@xa=\radius
    \advance\pgf@x by0.707107\pgf@xa
    \advance\pgf@y by0.707107\pgf@xa
  }%
  \anchor{south east}{
    \centerpoint
    \pgf@xa=\radius
    \advance\pgf@x by0.707107\pgf@xa
    \advance\pgf@y by-0.707107\pgf@xa
  }%
  \anchorborder{%
    \pgf@xa=\pgf@x%
    \pgf@ya=\pgf@y%
    \edef\pgf@marshal{%
      \noexpand\pgfpointborderellipse
      {\noexpand\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}
      {\noexpand\pgfqpoint{\radius}{\radius}}%
    }%
    \pgf@marshal%
    \pgf@xa=\pgf@x%
    \pgf@ya=\pgf@y%
    \centerpoint%
    \advance\pgf@x by\pgf@xa%
    \advance\pgf@y by\pgf@ya%
  }%

  %
  % Background path
  %
  \backgroundpath{%
    \pgfutil@tempdima=\radius%
    \pgfmathsetlength{\pgf@xb}{\pgfkeysvalueof{/pgf/outer xsep}}%
    \pgfmathsetlength{\pgf@yb}{\pgfkeysvalueof{/pgf/outer ysep}}%
    \ifdim\pgf@xb<\pgf@yb%
      \advance\pgfutil@tempdima by-\pgf@yb%
    \else%
      \advance\pgfutil@tempdima by-\pgf@xb%
    \fi%
    %%% This is added
    \pgfpathcircle{\centerpoint}{.7\pgfutil@tempdima}%
    \pgfusepath{fill}%
    %%%
    \pgfpathcircle{\centerpoint}{\pgfutil@tempdima}%
  }%
}%
\makeatother

\begin{document}

\begin{tikzpicture}[node distance=1.5cm]
  \node[UML/end](foo){};
  \node[UML/end, below=of foo]{};
  \node[UML/end] at (4, 0) {};
\end{tikzpicture}

\end{document}
6
  • Hi @Skillmon, thank you for your quick answer. Can I use it also als node, without coordinates?
    – Thomas K
    Oct 28, 2023 at 16:17
  • @ThomasK I added a variant that also allows using it as a node. But note that there is an apparent bug if you use the at (x, y) notation to position the node, as the append after command seems to get ignored then.
    – Skillmon
    Oct 28, 2023 at 16:25
  • Thank you for the improvement. Now I understand the design of a tikzset with multiply shapes. However, the bug affects also the relative positioning like "below of=...". Therefore, the approach is isn't useable for me. But thanks for your help.
    – Thomas K
    Oct 28, 2023 at 17:07
  • @ThomasK I've added a more complicated version using pgf-shapes that works with the positioning syntax. Note that you should use the syntax below=of ... in favour of the old below of=... syntax.
    – Skillmon
    Oct 28, 2023 at 18:19
  • @ThomasK added yet another working solution using the label key.
    – Skillmon
    Oct 28, 2023 at 18:37

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .