I'm trying to use \pgfpositionnodelater
to create and measure nodes before placing them. (Their size influences where they should be placed)
This works fine until I try to use \pgfpositionnode later with a node that has a \tikz[]{..}
environment in its body.
I'm using nested tikz environments because I have a number of complex macros that need to be nested very deeply, and trying to do this manually leads to an absurd amount of code for a diagram that could be specified much more cleanly.
I've got a minimum working example that tries to generate a diagram like this:
I want a set of macros that could be nested arbitrarily to create a tree like structure, where every parent box is tall enough to cover all its children. (Making this particular diagram without the features I use would be easy, However I'm trying to cover the general case.)
The Code that should generate the image, give or take some visible debugging bounding boxes and incorrectly set inner sep
values:
\documentclass[crop]{article}% 'crop' is the default for v1.0, before it was 'preview'
\usepackage{tikz}
\usepackage{xparse}
\usepackage{ifthen}
\usetikzlibrary{
positioning,
}
\makeatletter
\ExplSyntaxOn
% Define an identifier and a set of TIKZ commands so that you can get bounding
% box information before you place a node.
% #1 = identifier for the stuff you place
% #2 = tikz code you wish to place later
\DeclareDocumentCommand \placenodelater {d() +m}{
% Debug
\typeout{Starting_placenodelater_for_#1.}
% The macro the \pgfpositionnodelateruses as generated by the identifier
% given.
\cs_new_protected:cpn {pgf@#1-save-macro} {
% calculate width and height
\pgfmathsetmacro{\pgf@tempwid}{
\pgfpositionnodelatermaxx - \pgfpositionnodelaterminx
}
\pgfmathsetmacro{\pgf@temphei}{
\pgfpositionnodelatermaxy - \pgfpositionnodelaterminy
}
% Save all the location registers
\cs_gset_eq:cN {pgf@#1-name} \pgfpositionnodelatername
\cs_gset_eq:cN {pgf@#1-minx} \pgfpositionnodelaterminx
\cs_gset_eq:cN {pgf@#1-miny} \pgfpositionnodelaterminy
\cs_gset_eq:cN {pgf@#1-maxx} \pgfpositionnodelatermaxx
\cs_gset_eq:cN {pgf@#1-maxy} \pgfpositionnodelatermaxy
\cs_gset_eq:cN {pgf@#1-width} \pgf@tempwid
\cs_gset_eq:cN {pgf@#1-height} \pgf@temphei
\box_if_exist:cF {pgf@#1-box} {\box_new:c {pgf@#1-box}}
\box_gset_eq:cN {pgf@#1-box} \pgfpositionnodelaterbox
% Debug information
\typeout{__pgf@#1-save-macro_data_:}
\typeout{_____name___:_\use:c{pgf@#1-name}}
\typeout{_____min-x__:_\use:c{pgf@#1-minx}}
\typeout{_____max-x__:_\use:c{pgf@#1-maxx}}
\typeout{_____min-y__:_\use:c{pgf@#1-miny}}
\typeout{_____max-y__:_\use:c{pgf@#1-maxy}}
\typeout{_____height_:_\use:c{pgf@#1-height}}
\typeout{_____width__:_\use:c{pgf@#1-width}}
}
{
\exp_args:Nc \pgfpositionnodelater {pgf@#1-save-macro} #2
}
% Debug
\typeout{Ending_placenodelater_for_#1.}
}
% Place the node previously specified with the \placenodelater command
% #1 = identifier for nodes already saved by \placenodelater
% #2 = location for the node to be placed at
\DeclareDocumentCommand \placenodenow {d() +m}{
% Debug
\typeout{Starting_placenodenow_for_#1.}
% Move the various variables back
\cs_set_eq:Nc \pgfpositionnodelatername {pgf@#1-name}
\cs_set_eq:Nc \pgfpositionnodelaterminx {pgf@#1-minx}
\cs_set_eq:Nc \pgfpositionnodelaterminy {pgf@#1-miny}
\cs_set_eq:Nc \pgfpositionnodelatermaxx {pgf@#1-maxx}
\cs_set_eq:Nc \pgfpositionnodelatermaxy {pgf@#1-maxy}
\box_set_eq:Nc \pgfpositionnodelaterbox {pgf@#1-box}
% Call into PGF to place the node
\pgfpositionnodenow{#2}
% Debug information at time of placement
\typeout{__#1_data_:}
\typeout{_____name___:_\use:c{pgf@#1-name}}
\typeout{_____min-x__:_\use:c{pgf@#1-minx}}
\typeout{_____max-x__:_\use:c{pgf@#1-maxx}}
\typeout{_____min-y__:_\use:c{pgf@#1-miny}}
\typeout{_____max-y__:_\use:c{pgf@#1-maxy}}
\typeout{_____height_:_\use:c{pgf@#1-height}}
\typeout{_____width__:_\use:c{pgf@#1-width}}
% Debug
\typeout{Ending_placenodenow_for_#1.}
}
% #1 = Module Name
% #2 = Module Label
% #3 = Submodule definitions
\DeclareDocumentCommand \nestedmodule {d() +m +m} {
% If there was a module before us, we're right below it
% otherwise anywhere works
% \cs_if_exist_use:cTF {prevnode}
% {
% \tikzstyle{modulepositioning}=[
% ,anchor=north~west
% ,below=of~\use:c{prevnode}.south~west
% ]
% }
% {
% \tikzstyle{modulepositioning}=[]
% }
\ifthenelse{\isundefined\prevnode}
{
\tikzstyle{modulepositioning}=[]
}
{
\tikzstyle{modulepositioning}=[
,below=of~\prevnode.south~west
,anchor=north~west
]
}
% Create the enclosing node for our module
\node[
,rectangle
,modulepositioning
,draw,red
](#1){
\tikz[remember~picture]{
% Create all the submodules before we place them
\placenodelater(#1-mod-store){
% Create node containing submodules
\node[
,draw,blue
,anchor=west
,remember~picture
](#1modbox){
\tikz[remember~picture]{
\let\prevnode\undefined
% Place all subnodes
#3
}
};
}
\pgfmathsetmacro{\centralheight}{\use:c{pgf@#1-mod-store-height}}
% Create a main node that's large
\node[
,draw,black
,remember~picture
,inner~sep=5pt
,minimum~width=40pt
,minimum~height=\centralheight
](#1label){
#2
};
% % Place sub-modules
\placenodenow(#1-mod-store){\pgfpointanchor{#1label}{east}}
}
};
% Set self as previous for next node
% \cs_set_eq:cN {prevnode} {#1}
\def\prevnode{#1}
}
\ExplSyntaxOff
\makeatother
\begin{document}
\begin{tikzpicture}[remember picture,inner sep =1pt,node distance = 5pt]
\nestedmodule(root){root}{
\nestedmodule(port-1){p1}{
\nestedmodule(port-1-1){p11}{
};
\nestedmodule(port-1-2){p12}{
};
};
\nestedmodule(port-2){p2}{
};
};
\end{tikzpicture}
\end{document}
Playing with the code a little shows that using \placenodelater
with a normal node works fine, but the \tikz
environment in that node isn't
displayed. (Other sections of the label work, as do parallel elements.
Edit: I've managed to get a setup with PGF-2.1 working, and the above example (it changed with the edit)
The PGF-2.1 (and correct) output is:
The Broken output with PGF-3.0 is :
This is still a problem since I need tools that only work with PGF-3.0 in other parts of my document. I'm also really curious what difference (or set of difference) is causing the change.
I also have a full .sty
file for the type of diagram that I am actually trying to make, that used this technique to successfully generate diagrams.
However I updated TeXLive and it broke. I'm honestly not sure how to fix this, or even debug the issue, I think I've reduced it to the issue the above MWE shows and that I ask the question about, but I'm not 100% sure of that.
(A diagram and the .sty
file can be found here, these did work before I updated, even if I only created the MWE after the update. It tries to generate an image vaguely like one of the blocks in this image, Along with a random additional node, and a block that wraps those. I would post what it's actually supposed to create but I didn't save an image of the output before the update broke it.)
Edit : I've just rendered versions of this document's image with PGF-2.1 (approximately correct) and with PGF-3.0 (completely wrong).
Edit, May 13th:
Okay, this is driving me insane.
Further floundering has resulted in the following code:
% All earlier code is the same as before
% #1 = Module Name
% #2 = Module Label
% #3 = Submodule definitions
\DeclareDocumentCommand \nestedmodule {r() +m +m} {
% If there was a module before us, we're right below it
% otherwise anywhere works
% \cs_if_exist_use:cTF {prevnode}
% {
% \tikzstyle{modulepositioning}=[
% ,anchor=north~west
% ,below=of~\use:c{prevnode}.south~west
% ]
% }
% {
% \tikzstyle{modulepositioning}=[]
% }
\ifthenelse{\isundefined\prevnode}
{
\tikzstyle{modulepositioning}=[]
}
{
\tikzstyle{modulepositioning}=[
,below=of~\prevnode.south~west
,anchor=north~west
]
}
% Create the enclosing node for our module
\node[
,rectangle
,modulepositioning
,draw,red
](#1){
\tikz[remember~picture]{
% Create all the submodules before we place them
\placenodelater(#1-mod-store){
% Create node containing submodules
\node[
,draw,blue
,anchor=west
,remember~picture
](#1modbox){
\tikz[remember~picture]{
\placenodelater(#1-welp){ % Make sure no prvious node is defined
\let\prevnode\undefined
% Place all subnodes
\node[draw,orange]{test};
#3
}
\placenodenow(#1-welp){\pgfpointorigin}
}
};
}
\pgfmathsetmacro{\centralheight}{\use:c{pgf@#1-mod-store-height}}
% Create a main node that's large
\placenodelater(#1-more-welp){
\node[
,draw,green
,remember~picture
,minimum~height=\centralheight
](#1label){
#2
};
}
\placenodenow(#1-more-welp){\pgfpointorigin}
% % Place sub-modules
\placenodenow(#1-mod-store){\pgfpointanchor{#1label}{east}}
}
};
% Set self as previous for next node
% \cs_set_eq:cN {prevnode} {#1}
\def\prevnode{#1}
}
\ExplSyntaxOff
\makeatother
\begin{document}
\begin{tikzpicture}[remember picture]
\nestedmodule(root){root}{
\nestedmodule(port-1){p1}{
\nestedmodule(port-1-1){p11}{
};
\nestedmodule(port-1-2){p12}{
};
};
%\nestedmodule(port-2){p2}{
%};
};
\end{tikzpicture}
\end{document}
With the following result:
What I did was take every nested instance of a \tikz
environment and
stick another \placenodelater
,\placenodenow
pair in there.
This somewhat works, but it makes the actual issue more obvious. Namely, \pgfpositionnodelater
will only save the last node in the scope it's called it, instead of taking all of them and putting a bounding box around it.
When I was using a nested \tikz
environment I was placing the nodes in that nested environment, and then the external container node. Because only the last one is placed, only it is rendered.
The only solution that comes to mind is using \positionnodelater
for every internal node and manually replicating the effects of the positioning
library. This would be incredibly awkward, and would require me to override most of the core tikz constructs, so I'm still looking for a way to solve this.
Edit: I've posted a workaround as an answer, but I would still appreciate a genuine solution to the problem. As it is there's too many hacky things going on at any point for it to be robust, or easy to use.
\tikz
tries to reset itself with half of the settings inherited from the environment. The TikZ way is to place things according to the parent size: painstaking but a sure way. – percusse May 14 '15 at 8:35forest
package. However I couldn't find the right hook where something likespan vertical={(forest cs:go=first)(forest cs:go=last)}
could work right. Either it didn't do anything, or it couldn't find the nodes.forest
internally works similar as it typesets all nodes beforehand and measures them to find the right placement. – Qrrbrbirlbel May 15 '15 at 23:37