14

I'm confused about tikz pics. I can freely define them without issue, and even add coordinates inside that will be available by name inheritance like (-A) as for example in \pic (S) at (0,0) {myPic}; \draw (S-A) -- ++(0,0);. This is all documented around p. 255 of the pgf manual.

However I don' understand why it is not possible to give a name to a pic like the one above if no coordinate is defined inside it. By default it would be great if it could behave like a normal node.

MWE:

\documentclass{minimal}
\usepackage{tikz}

\tikzset{
    myBlock/.pic = {
        \node[draw,minimum width=1cm,minimum height=0.5cm] at (0,0) {};
    },
    myBlockNamed/.pic = {
        \node[draw,minimum height=1cm,minimum width=0.5cm] (-m) at (0,0) {};
    },
}

\begin{document}
\begin{tikzpicture}
    \pic () at (0,0) {myBock}; % can't give it a name
    \pic (S) at (3,0) {myBlockNamed}; % can give it a name
    \draw (S-A) -- ++(2,0);
\end{tikzpicture}
\end{document}

The underlying point is I'm working on a control blocs library (so that we can stop the chat below with @Alenanno). For example here is the definition of a math operator

\tikzset{
    mySum background/.style = {
        circle, minimum size=1.5em, fill=white,
    },
    mySum edge/.style = {
        draw=black, circle, minimum size=1.5em, line width=.5pt,
    },
    pics/bOp/.style = {
    code = {
      \node [mySum background] {};
      \foreach \t [count=\i] in {#1}{
        \pgfmathsetmacro{\angle}{\i*90}
        \node[anchor=center, font=\tiny] at (\angle:0.45em) {$\t$};
      }
      \node [mySum edge] {};
    },
  },
}
  • pic is the mechanism to include a predefined drawing. It is not a referable object. Unless there is something defined in it, that is. Think of it as a container. And your example needs some corrections to be compiled e.g., \pic at (0,0) {myBlock};\pic (S) at (3,0) {myBlockNamed};\draw (S-m) -- ++(2,0); works. The first one you need to remove the at (0,0) from the definition – percusse May 2 '15 at 9:01
  • As far as I know you cannot refer to a pic directly. Is there a reason why you're using that instead of a standard node? – Alenanno May 2 '15 at 9:02
16

There is a trick that can be used in some circumstances which exploits the fact that a pic is put inside a scope which can be named with the local bounding box key. Note that this will not work in trees or graphs.

\documentclass[tikz,border=5]{standalone}
\tikzset{pics/.cd,
  pic a/.style={code={
      \node [fill=red!20, shape=circle] {A};
  }}
}

\begin{document}
\begin{tikzpicture}
\pic [local bounding box=A1] at (0,0) {pic a};
\pic [local bounding box=A2] at (3,0) {pic a};
\foreach \i in {0,15,...,345}{
  \draw [red, ->] (\i:1) -- (A1);
  \draw [blue, ->] (3,0)++(\i:1) -- (A2);
}
\end{tikzpicture}
\end{document}

enter image description here

It is also possible to use the name given to the pic by defining a "wrapper" key which explicitly adds a scope to the code:

\documentclass[tikz,border=5]{standalone}
\usetikzlibrary{fit}%
\makeatletter
\tikzset{pics/named scope code/.style={code={\tikz@fig@mustbenamed%
  \begin{scope}[local bounding box/.expanded=\tikz@fig@name]#1\end{scope}%
}}}
\makeatother

\tikzset{pics/.cd,
  pic a/.style={named scope code={
      \node [fill=red!20, shape=circle] {A};
  }}
}

\begin{document}
\begin{tikzpicture}
\pic (A1) at (0,0) {pic a};
\pic (A2) at (3,0) {pic a};
\foreach \i in {0,15,...,345}{
  \draw [red, ->] (\i:1) -- (A1);
  \draw [blue, ->] (3,0)++(\i:1) -- (A2);
}
\end{tikzpicture}
\end{document}

The result is the same as before.

  • Got a quick question. Does the second method (explicitly adding a scope to the picture) also won't work with trees and graphs? – user101590 Sep 5 '16 at 0:43
  • Don't worry. I tried it, but the tree can't be built as there's a lot of problems with the node shape names (the lines that connect the nodes and build the tree). – user101590 Sep 5 '16 at 1:12
3

From the Pgf Manual (version 3.0.0), page 252:

  1. Unlike nodes, pics cannot be referenced later on. You can reference nodes that are inside a pic, but not “the pic itself.” In particular, you cannot draw lines between pics the way you can draw them between nodes. In general, whenever it makes sense that some drawing could conceivably be connected to other node-like-things, then a node is better than a pic.

  2. If pics are used to emulate the full power of a node (which is possible, in principle), they will be slower to construct and take up more memory than a node achieving the same effect.

So, if you're doing something a node does well, and there is no particular reason for using pic, you're better off using a node. There is a workaround in the answer to the question "How to give a name to \pic", but it's exactly that, a workaround.

However, if you want to be able to position nodes around another node, you can check this question I asked some time ago (How to position nodes around another node in TiKZ?) or you use a Tikz foreach with a regular node.

Updated answer

Now that I know what you need, here's a solution with Tikz. The newcommand \mySumn (you can rename it) takes two arguments like \mySumn{#1}{#2}, where #1 is the node's name, and #2 is the position.

I tried to add an optional argument using above of but didn't succeed. However you can do the same with this code but in a different way. Consider the following:

\mySumn{p1}{0,0};
\mySumn{p2}{$(p1.north east)+(1em,1em)$};

It will place a node p1 at the coordinates (0,0) and then it will place another node p2 at p1's north east +1em on the x and the y axis (diagonally above on the right).

Of course you can still draw edges from one node to the other.

Output

figure 1

Code

\documentclass[margin=10pt]{standalone}
\usepackage{tikz}
\usepackage{xparse}

\usetikzlibrary{calc,arrows.meta,positioning}

\tikzset{
    mySumbk/.style = {
        draw, circle, minimum size=1.5em, fill=white,
    },
}

\newcommand{\mySumn}[2]{
    \node[mySumbk,fill=white] (#1) at (#2) {};
        \foreach \angle/\maop in {
        90/+,
        180/-
        }{
   \node[anchor=center, font=\tiny] at ($(#1)+(\angle:.45em)$) {$\maop$};
}}

\begin{document}
\begin{tikzpicture}[>=latex]

\mySumn{p1}{0,0};
\mySumn{p2}{$(p1.north east)+(1em,1em)$};

\draw (p1) -- (3,0);
\draw (p1) edge[out=90,in=180] (p2);
\end{tikzpicture}
\end{document}
  • The thing is the syntax becomes ugly if one has to use a node: \node (S) at (0,0) {\tikz{\draw pic {myBlock};}};which is all but convenient. – s__C May 2 '15 at 9:56
  • @s__C Why not simply \node[myBlock] (S) at (0,0) {};? Of course, you'd need to fix the \tikzset accordingly. – Alenanno May 2 '15 at 9:57
  • Then I cannot define anymore a complex myBlock. I used a rectangle in the MWE just to keep it simple... I have pics with arguments etc. – s__C May 2 '15 at 10:01
  • Well, then you should mention exactly what you're using. :P What is the shape like? You can probably still do it without using a pic. – Alenanno May 2 '15 at 10:04
  • Its a rectangular shape with \draw commands inside, so I really doubt I can bypass defining pics... – s__C May 2 '15 at 10:05
0

It appears that if I define a command like for my operator:

\tikzset{
    mySum background/.style = {
        circle, minimum size=1.5em, fill=white,
    },
    mySum edge/.style = {
        draw=black, circle, minimum size=1.5em, line width=.5pt,
    },
}
\newcommand{\bOp}[1]{
  \tikz[outer sep=0pt]{
    \node [mySum background] {};
    \foreach \t [count=\i] in {#1}{
      \pgfmathsetmacro{\angle}{\i*90}
      \node[anchor=center, font=\tiny] at (\angle:0.45em) {$\t$};
    }
    \node [mySum edge] {};
  }
}

It is then possible to create a node that contains a fake pic, as what is above is exactly what one would use to define a pic.

\node (1) at (0,0) {\bOp{+,-,,}};
\draw (4,0) -- (1);
  • Your code does not compile (and I'm still not sure what you want to do). :P – Alenanno May 2 '15 at 16:01
  • It should compile now... – s__C May 2 '15 at 16:32
  • Nesting TikZ pictures is a Bad Idea. – cfr May 2 '15 at 16:39
  • Do you have a better one @cfr? – s__C May 2 '15 at 18:38
  • No. But Mark Wibrow already explained one. – cfr May 2 '15 at 19:22
0

Old question, but I was not happy with the local bounding box solution since I couldn't find a way to have a better control over it (distance to the inner content, for instance).

My solution was to use a nested picture in the pic, together with the remember picture option. The names are passed as arguments in a cascading fashion. With this, you can not only reference the pic as a node (because in fact it is a node) but also reference its contents.

Another point is that with this solution the content of a pic is always centralized, which was also that I was looking for when working on this code.

Be aware:

  • you have to compile the document twice because of remember picture.
  • nested nodes can be tricky because of inheritance.

enter image description here

Here is the code:

\documentclass[tikz]{standalone}

\usetikzlibrary{positioning}

\begin{document}
\begin{tikzpicture}[remember picture]
  \tikzset{
    mine/.pic={
      \node[draw](#1){
        \begin{tikzpicture}[remember picture]
          \node[draw](#1-1){#1-1};          
          \node[draw, right=of #1-1](#1-2){#1-2};
        \end{tikzpicture}
      };
    },
  }

  \pic{mine=a};
  \pic[right=of a]{mine=b};

  \draw[thick, blue, <->](a-1)to[out=90, in=90](b-1);
  \draw[thick, blue, <->](a-2)to[out=-90, in=-90](b-2);
  \draw[thick, red](a)to(b);
\end{tikzpicture}
\end{document}
  • Don't nest TikZ pictures. There is always a better way. – percusse Jan 20 '18 at 17:02
  • @percusse Well, I also don't like to nest pictures. In my answer I pointed the reasons why I chose that path. I will be happy to learn about other ways to do that with those points in mind. – tcpaiva Jan 20 '18 at 17:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.