4

I'm trying to create pics that behave like nodes. Following the instructions of shifting scopes and naming scopes for pics, I managed to manually create pics and move them to behave like nodes.

The main problem, is that if the pic is not constructed in a way that the first node remains at the center of the scope, then the whole pic is shifted (see my example below).

My problem is how to automatically re-center the scope such that it doesn't matter how the pic was constructed the anchor will always be at its center. Is there a way to center the scope like using the pic shift but automatically?

MWE

\documentclass[convert={outext=.png}]{standalone}

\usepackage{tikz}
\usetikzlibrary{positioning}

\begin{document}
\makeatletter
\begin{tikzpicture}[
  % https://tex.stackexchange.com/a/185283/7561
  pic shift/.store in=\shiftcoord,
  pic shift={(0,0)},
  % https://tex.stackexchange.com/a/241737/7561
  pics/named scope code/.style 2 args={code={\tikz@fig@mustbenamed%
      \begin{scope}[shift={\shiftcoord}, local bounding box/.expanded=\tikz@fig@name, #1]#2\end{scope}%
  }},
  % this pic is shifted since the other nodes are placed at the right of the first one
  pics/balls/.style = {named scope code={node distance=5pt}{%
      \node[draw, circle] (c1) {};
      \node[draw, circle, right=of c1] (c2) {};
      \node[draw, circle, right=of c2] (c3) {};
  }},
  % same as the above pic, but now the nodes are placed around the first one instead
  pics/center balls/.style = {named scope code={node distance=5pt}{%
      \node[draw, circle] (c1) {};
      \node[draw, circle, left=of c1] (c2) {};
      \node[draw, circle, right=of c1] (c3) {};
  }},
  ]
  
  % base node for reference
  \node (o) {O};
  % first pic
  \draw pic[right=of o] (b) {balls};
  % this is shifted
  \draw pic[below=5pt of b] (b2) {balls};
  % this is centered by moving it by hand 
  \draw pic[below=20pt of b, pic shift={(-.5,0)}] (b2) {balls};
  % this is centered by design
  \draw pic[below=35pt of b] (b3) {center balls};
\end{tikzpicture}
\makeatother
\end{document}

enter image description here

3
  • Is this related to tex.stackexchange.com/q/185279/86 ? Commented Aug 29, 2021 at 7:45
  • @AndrewStacey that is the example I followed to do the shifting.
    – adn
    Commented Aug 29, 2021 at 11:10
  • My answer at that question would work for you here: just use the centre of the local bounding box as the anchor. Commented Aug 29, 2021 at 13:15

2 Answers 2

6

I guess that this is a centering problem I encountered before: trying to put a non-centered pic in the center of a node. If so, my solution is making a \savebox, then later use \usebox.

enter image description here

\documentclass[tikz,border=5mm]{standalone}
\usetikzlibrary{matrix}
\begin{document}
\begin{tikzpicture}
\tikzset{cube/.pic={
\path
(0,0) coordinate (A1)
(1,0) coordinate (A2)
(1,1) coordinate (A3)
(0,1) coordinate (A4);
\foreach \i in {1,...,4}
\path (A\i)+(45:.5) coordinate (B\i);
\draw[pic actions] (A1)--(A2)--(B2)--(B3)--(B4)--(A4)--cycle;
\draw[dashed] (B1)--(A1) (B1)--(B2) (B1)--(B4);
\draw (A3)--(A4) (A3)--(A2) (A3)--(B3);
}}
 
\newsavebox{\tempbox}
\savebox{\tempbox}{\tikz{\pic[fill=yellow!30]{cube};}}
% later use \usebox{\tempbox} to center the cube

\draw[gray!50] (0,0) grid (6,3);
\path (1,1) pic{cube};
\path (4,1) node{\usebox{\tempbox}};

\fill[red] 
(1,1) circle (2pt) (4,1) circle (2pt);

\end{tikzpicture}
\end{document}

enter image description here

\documentclass[tikz,border=5mm]{standalone}
\usetikzlibrary{matrix}
\begin{document}
\begin{tikzpicture}
\tikzset{cube/.pic={
\path
(0,0) coordinate (A1)
(1,0) coordinate (A2)
(1,1) coordinate (A3)
(0,1) coordinate (A4);
\foreach \i in {1,...,4}
\path (A\i)+(45:.5) coordinate (B\i);
\draw[pic actions] (A1)--(A2)--(B2)--(B3)--(B4)--(A4)--cycle;
\draw[dashed] (B1)--(A1) (B1)--(B2) (B1)--(B4);
\draw (A3)--(A4) (A3)--(A2) (A3)--(B3);
}}
 
\newsavebox{\tempbox}
\savebox{\tempbox}{\tikz{\pic[scale=1.5,fill=yellow!30]{cube};}}
% later use \usebox{\tempbox} to center the cube

\matrix (m) [matrix of nodes,
nodes in empty cells,
row sep=-\pgflinewidth,
column sep=-\pgflinewidth,
nodes={minimum height=10mm,draw=magenta!50,anchor=center},
column 1/.style={nodes={minimum width=8 cm,text width=6cm,align=center}},
column 2/.style={nodes={minimum width=4 cm}},
row 2/.style={nodes={minimum height=3cm}},
]{
Volume&Figure\\
Volume of the cube $V=a^3$&\usebox{\tempbox}\\
};
\end{tikzpicture}
\end{document}
2
  • In this case the pic is fixed since you are putting it within a node, right? I'm looking for something more automatic, but I will try and see if I can make the box creation part of the pic.
    – adn
    Commented Aug 29, 2021 at 11:13
  • @adn Could you give a concrete example? (other than the one in your question)
    – Black Mild
    Commented Aug 31, 2021 at 1:38
2

Here's a solution using the new facility in the tikzmark library for repositioning pics, as introduced at Anchoring TiKZ pics. At time of writing, this isn't yet on CTAN so download tikzmark.dtx from github and run tex tikzmark.dtx to generate the files.

\documentclass{article}
%\url{https://tex.stackexchange.com/q/612364/86}
\usepackage{tikz}
\usetikzlibrary{
  positioning,
  tikzmark,
  fit
}

\makeatletter
\tikzset{
  save pic bounding box/.code={
    \tikz@fig@mustbenamed
    \tikzset{local bounding box/.expanded=\tikz@fig@name}
  }
}
\makeatother

\begin{document}
\begin{tikzpicture}[
  balls/.pic={
    \begin{scope}[adjust pic position, node distance=5pt, save pic bounding box]
      \node[draw, circle] (c1) {};
      \node[draw, circle, right=of c1] (c2) {};
    \node[draw, circle, right=of c2] (c3) {};
    \end{scope}
  },
  % same as the above pic, but now the nodes are placed around the first one instead
  center balls/.pic={
    \begin{scope}[adjust pic position, node distance=5pt, save pic bounding box]
      \node[draw, circle] (c1) {};
      \node[draw, circle, left=of c1] (c2) {};
    \node[draw, circle, right=of c1] (c3) {};
    \end{scope}
  },
  ]
  
  % base node for reference
  \node (o) {O};
  % first pic
\draw pic[right=of o] (b) {balls};
\node[fit=(b),draw,inner sep=0pt,node contents={}];
\draw (b.south) -- ++(0,-5pt);
\draw[red] ([xshift=1pt]b.south) -- ++(0,-20pt);
\draw[blue] ([xshift=-1pt]b.south) -- ++(0,-35pt);
  % Note that the `below=5pt of b` means that the default anchor in the pic is set to `north`
  \draw pic[below=5pt of b] (b2) {balls};
  % this is centred using tikzmark
  \draw pic[below=20pt of b, pic anchor={(b3.center)}] (b3) {balls};
  % this is centred by design
  \draw pic[below=35pt of b] (b4) {center balls};
\end{tikzpicture}

\end{document}

The key save pic bounding box is only needed to be able to refer to a pic's location outside of that pic. It isn't needed for just positioning a pic.

I noticed one odd thing when compiling your code. By using the below=5pt of b key on the pic, you effectively set the default anchors for the nodes inside the pic to north. To show the effect of that, I've drawn lines down from the first pic stretching 5pt, 20pt, and 35pt. In the second and fourth sets of circles, they are positioning below the extent of their line. In the third (which uses the tikzmark positioning) then they are centred at the end of the line. That's because the tikzmark positioning system says "Place the given coordinate at the pic's position" and so once the position has been calculated (from the below=20pt of b) then the given coordinate is placed there. To get the behaviour one might expect of a node, use pic anchor=(b3.north).

Centring pics using tikzmark


A bit more experimenting leads to the following key:

  auto pic anchor/.style={
    pic anchor={(\tikz@fig@name.\tikz@anchor)}
  }

which should be invoked after using below=5pt of b (or whatever) and instead of the pic anchor key and which then picks up the anchor set by the below key and applies it to the pic anchor positioning system. (It needs a bit more testing to ensure that there aren't situations in which the \tikz@fig@name and \tikz@anchor can get overwritten since they are set at this point but the pic anchor isn't actually used until after the pic has been drawn; I think that the scoping means that anything inside the pic won't affect them but it would be good to be sure.)

You must log in to answer this question.

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