15

The following example draws three rectangles, each divided in two parts (they're Lisp cons cells). It works fine, but something doesn't seem right: I have used \begin{scope} inside the \cons macro, and inside that macro I have two nodes named a and b. However, if I use the names a and b outside the macro, things become confusing. Aren't the two names a and b inside \begin{scope} there supposed to be invisible to the rest of the document?

The example works fine, but changing aa to a, and so on exposes the problem.

Is there a better or easier way to do what I'm trying to do?

update: So, names are defined globally anyway... I'd like to use the \cons macro several times in the same picture -- which doesn't seem to work if node names are global. What can I do then? Should I use only relative positioning and not give the nodes any names? Is this the only way?

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{shapes,positioning}

\begin{document}

\newcommand{\cons}{%
\begin{tikzpicture}[cell/.style={draw,shape=rectangle,minimum size=0.4cm}]
\begin{scope}
\node[cell](a)[anchor=west]{};
\node[cell](b)[right=0mm of a.east,anchor=west]{};
\end{scope}
\end{tikzpicture}
}

\begin{tikzpicture}
\node(aa){\cons};
\node(bb)[below left=of aa]{\cons};
\node(cc)[below right=of aa]{\cons};
\draw[->,red]  (aa.west) -| (bb.north);
\draw[->,blue] (aa.east) -| (cc.north);
\end{tikzpicture}

\end{document}
6
  • 3
    No, the node names are defined globally regardless of the scoping.
    – percusse
    Commented Jun 29, 2012 at 20:57
  • Oh, I see. I misunderstood what the scoping was about then... I'll update my question.
    – Jay
    Commented Jun 29, 2012 at 21:01
  • 3
    You can reuse node names without breaking anything so long as you don't need to refer to a previous incarnation of a particular node. So there's no difficulty with reusing the \cons macro from that point of view. (However, nesting tikzpictures is fraught with difficulty and it can be better to find an alternative that doesn't involve nesting, such as percusse's suggestion). Commented Jun 29, 2012 at 21:07
  • 1
    But there still is the question of why nodes are always defined globally, defying the meaning of scope. I assume there is a reason.
    – Ahmed Musa
    Commented Jun 30, 2012 at 16:48
  • 2
    @AhmedMusa For the very simple reason that scopes are often used for other reasons than grouping. It is common to use a scope to mean "Apply the following settings to all the following parts of the diagram", such as a style or a transformation. When applying this to nodes or coordinates, one wants to use them in the rest of the picture. I would expect to be able to use all nodes names within a tikzpicture with no regard to scoping, and since it is not expected that tikzpictures should be nested, making node names global is sensible. Commented Jun 30, 2012 at 19:12

4 Answers 4

10

You can use the readily available multipart node shapes.

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{shapes.multipart,positioning}

\begin{document}


\begin{tikzpicture}[twocell/.style={draw,
                                    rectangle split, 
                                    rectangle split horizontal,
                                    rectangle split parts=2
                                   }
]
\node[twocell](aa){};
\node[twocell](bb)[below left=of aa]{};
\node[twocell](cc)[below right=of aa]{};
\draw[->,red]  (aa.west) -| (bb.north);
\draw[->,blue] (aa.east) -| (cc.north);
\end{tikzpicture}

\end{document}

enter image description here

2
  • Thanks -- that is certainly useful! It would be also nice if I could get rounded corners -- not only on the outer corners, but also in the split (as if they were really two squares with rounded corners). But this is already great!
    – Jay
    Commented Jun 29, 2012 at 21:06
  • 1
    @Jay that's a little bit tricky as you have to define a new shape. But if you are not going to write anything inside these, then maybe some extra tricks can be pulled off.
    – percusse
    Commented Jun 29, 2012 at 21:08
7

Jay, I won't answer your original question, because Percusse did it, but the one you made in you comment: How to get cons rounded corners.

(I'm not sure if would be better to make a 'following' question and answer there. Let's see what moderators consider)

As Percusse suggestet, the solution is tricky. You use a non drawn multipart rectangle and draw rounded rectangles over each part with append after command option. Here you will find several answers using this option. In particular, I've taken code from Add new symbol to simple TikZ diagram and Problem with "append after command" and "insert path". I don't understand why command \pgfextra is necessary but it woks.

Here you have it:

\documentclass{standalone}

\usepackage{tikz}
\usetikzlibrary{shapes.multipart,positioning}

\begin{document}
\begin{tikzpicture}[%
cons/.style={%
    rectangle split,%
    rectangle split horizontal,%
    rectangle split parts=2,%
   outer sep=0pt,%
    append after command={%
    \pgfextra{%
     \draw[rounded corners=3pt] (\tikzlastnode.south west)      
          rectangle (\tikzlastnode.one split north);%
     \draw[rounded corners=3pt] (\tikzlastnode.north east)   
          rectangle (\tikzlastnode.one split south);}}}]
\node[cons](aa){};
\node[cons](bb)[below left=of aa]{};
\node[cons](cc)[below right=of aa]{};
\draw[->,red]  (aa.west) -| (bb.north);
\draw[->,blue] (aa.east) -| (cc.north);
\end{tikzpicture}
\end{document}

enter image description here

2
  • 3
    PGF manual on \pgfextra: "Note that this command should only be used by real experts and should only be used deep inside clever macros, not on normal paths." Hence we deduce that Ignasi is an "expert".
    – Ahmed Musa
    Commented Jun 30, 2012 at 16:56
  • @AhmedMusa You can see in my answer that you can avoid the use of \pgfextra. Commented Jul 1, 2012 at 9:03
6

It's possible to simplify the code because you use append after command without pgfextra.

You need to create a pathwith the option draw then you add a node to the path with the option cons. With this method you avoid the use of \draw inside ``append after command` .

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{shapes.multipart,positioning}

\begin{document}
\begin{tikzpicture}[%
cons/.style={%
    rectangle split,%
    rectangle split horizontal,%
    rectangle split parts=2,%
    outer sep=0pt,%
    append after command={%
     [rounded corners=3pt] (\tikzlastnode.south west)  rectangle (\tikzlastnode.one split north)
                           (\tikzlastnode.north east)  rectangle (\tikzlastnode.one split south)  
          }}]
\path [draw] node[cons](aa){};
\path [draw] node[cons](bb)[below left=of aa]{};
\path [draw] node[cons](cc)[below right=of aa]{};

\draw[->,red]  (aa.west) -| (bb.north);
\draw[->,blue] (aa.east) -| (cc.north);  
\end{tikzpicture}
\end{document}  

enter image description here

2
  • Before posting my answer with \pgfextra I tested without it and without success: \draw failed and \path node also but I didn't know \path [draw]. Thank you for showing it!
    – Ignasi
    Commented Jul 1, 2012 at 16:31
  • @Ignasi A node is not really a part of a path. It's an object added to the path and the node add is personal option draw. So when you can draw a path but not the nodes added along the path. Commented Jul 1, 2012 at 18:32
6

In response to Ignasi's remark that he doesn't know what \pgfextra does, it suspends path parsing or tracing so that an arbitrary code can be executed. This is risky or impossible otherwise. I demonstrate the use of an arbitrary code in the following.

After a lengthy trace, I saw that when using \pgfextra it is necessary to avoid spurious spaces, such as the space after \pgfextra{. Those spaces are not removed internally. Ignasi did it correctly in his solution, but it is easy to overlook. I hope PGF developers will attend to this internally later. In the meantime, when \pgfextra comes in the argument of at end node, the following solution can be used.

EDIT: I present the full code below in response to Altermundus' comment.

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{shapes.multipart,positioning}
\makeatletter
% If we load pgfkeyx.sty, we can normalize the list before parsing.
% Well, let's ignore difficult lists here.
% Using the delimiter \pgfeov for the callback of \pgfkeyscommaloop allows it 
% to be multi-parametered.
\def\pgfkeyscommaloop#1#2{\pgfkeys@commaloop{#1}#2,\pgfkeyscommaloop,}
\def\pgfkeys@commaloop#1#2,{%
  \expandafter\ifx\@car#2x\@nil\pgfkeyscommaloop
    \expandafter\@gobble\else\expandafter\@iden\fi
  {#1#2\pgfeov\pgfkeys@commaloop{#1}}%
}
\def\ifpgfkeydef#1{%
  \ifcsname pgfk@#1/.@cmd\endcsname
    % OK, the command is already in the hash table; no safeguard needed
    % against filling the hash.
    \expandafter\ifx\csname pgfk@#1/.@cmd\endcsname\relax
      \expandafter\expandafter\expandafter\@secondoftwo
    \else
      \expandafter\expandafter\expandafter\@firstoftwo
    \fi
  \else
    \expandafter\@secondoftwo
  \fi
}
\def\pgfkey@def@do#1\pgfeov{\ifpgfkeydef{#1}{-1}{+0}}
\def\pgfkey@undef@do#1\pgfeov{\ifpgfkeydef{#1}{+0}{-1}}
% Given a list of keys, I want to know if at least one of them is defined:
\def\ifonepgfkeydef#1{%
  \ifnum\numexpr\z@\pgfkeyscommaloop\pgfkey@def@do{#1}<\z@
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}
% Given a list of keys, I want to know if at least one of them is undefined:
\def\ifonepgfkeyundef#1{%
  \ifnum\numexpr\z@\pgfkeyscommaloop\pgfkey@undef@do{#1}<\z@
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}
\ifonepgfkeydef{/tikz/at end of node,/tikz/at end node}{%
  \@latexerr{Key(s) '/tikz/at end of node' and/or
    '/tikz/at end node' already exist}\@ehd
}{}

% The following acrobatics is just an academic exercise: Altermundus's approach
% is simpler and better:
\tikzset{at end of node/.code=
  \begingroup
  \def\pgf@@relax{\relax}%
  \pgfkeys@spdef\reserveda{#1}%
  \def\reservedb##1\pgfextra##2##3\pgf@nil{%
    \ifx\pgf@@relax##2\else
      \pgfkeys@spdef\reservedb{##2}%
      \def\reservedc##1\pgfextra\pgf@@relax{##1}%
      \edef\reserveda{\unexpanded{##1}\noexpand\pgfextra{\unexpanded
      \expandafter{\reservedb}}\unexpanded\expandafter{\reservedc##3}}%
    \fi
  }%
  \expandafter\reservedb\reserveda\pgfextra\pgf@@relax\pgf@nil
  \edef\reserveda{\endgroup
    \noexpand\pgfkeysalso{append after command=%
    {\unexpanded\expandafter{\reserveda}}}%
  }\reserveda
  ,
  at end node/.style={at end of node={#1}}
}
\def\minorrect#1#2#3{%
  \draw[rounded corners=#1] (\tikzlastnode.#2)
    rectangle (\tikzlastnode.one split #3);
}
\makeatother

\begin{document}
\begin{tikzpicture}[cons/.style={%
  rectangle split,
  rectangle split horizontal,
  rectangle split parts=2,
  outer sep=0pt,
  at end of node={
    \pgfextra{
      \minorrect{3pt}{south west}{north}%
      \minorrect{10pt}{north east}{south}%
    }%
  }%
}%
]
\node[cons](aa){};
\node[cons](bb)[below left=of aa]{};
\node[cons](cc)[below right=of aa]{};
\draw[->,red]  (aa.west) -| (bb.north);
\draw[->,blue] (aa.east) -| (cc.north);
\end{tikzpicture}
\end{document}

enter image description here

1
  • First, I try your last solution to avoid spurious spaces but I can't compile. Can you give the complete code ? Secondly, I you place % at the end of the definition of \minorrect with }%, I think you will remove the first spurious space. Commented Jul 1, 2012 at 7:47

You must log in to answer this question.

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