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

adn
  • 11,233

2 Answers2

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}

Black Mild
  • 17,569
  • 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 Aug 29 '21 at 11:13
  • @adn Could you give a concrete example? (other than the one in your question) – Black Mild Aug 31 '21 at 01: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.)

Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751