[Tex/LaTex] TikZ: Make node height span several others

calculationsheightnodestikz-pgf

How can I make the Z node span the total height of the ABCD column?

nodes

My current code is:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc,positioning}

\begin{document}
\begin{tikzpicture}[node distance=5pt,every node/.style={draw, minimum width=100pt}]
  \node(A)             {A};
  \node(B)[below=of A] {B};
  \node(C)[below=of B] {C};
  \node(D)[below=of C] {D};

  \path let \p1=(A.north), \p2=(D.south) in
    node[minimum height={\y2-\y1},right=of A] {Z};
\end{tikzpicture}
\end{document}

For some reason, the calculated minimum height is not working.

I read in the threads

that I should not use minimum height but text height instead (but I don't understand why). However, that doesn't help either.

Best Answer

Both solutions, either with fit or with calc, have their disadvantages. The fit solution needs fixing with inner ysep and more importantly outer ysep while the calc solution needs extra fiddling outside the actual node (with the \path let … in).

Let’s combine them. The fit key basically scans all coordinates/nodes it has been given. If it encounters a node the node’s anchors west, east, north and south are scanned separately. If not, the given point is just that: a one-dimensional coordinate. At the end four dimensions are set to the height and lowest x and y value. We simply have a rectangle in the canvas plane.

We can use these four dimensions

  • to calculate the spanned vertical or horizontal length and
  • to create a pseudo-node. Similar to the path picture bounding box, a fit bounding box will be created locally. This makes it perfect to use it with the already well-defined position keys like left and right.

This answer introduces four keys and one enhancement of the keys of positioning library (only with of, of course).

The keys are:

  • fit bounding box: creates a rectangular node called fit bounding box around the given nodes. Syntax like the fit key. This node is defined locally. If you want to use it in following paths use it inside a \tikzset.

  • span vertical and span horizontal: they issue minimum height or minimum width respectively on the given nodes (same syntax like fit).

  • span: span vertical and span horizontal (without scanning the coordinates/nodes twice)

The enhancement of the positioning keys work similar:

If the part after of starts with an open parenthesis ( it acts as a switch between the usual behavior (just placement) and fitting around one or more nodes. That means, the placement is relatively to a rectangular box around all given nodes.

If the part after of starts with a |, a - or a + then the created node is also fitted vertically (|), horizontally - or vertically and horizontally + to the given node(s) (if its only one node you can omit the parentheses ( ) as in

\node[span vertical=(A)(B), above=of -X] {y};

Take a look at all given examples and also change the shape of every node to see how it affects different node shapes.


A previous solution I had in mind was actually dependent on the fact that all nodes you want to span have the same horizontal extent and you had to exactly give the most upper and the most lower node (the latter could have easily resolved but the fit library already handles this excellently). It also used something similar like right=of <upper node>.north east, anchor=north west but TikZ-ified with the north right or the corner north right (for circles) key from my positioning-plus library which might be helpful in some cases nonetheless.

The actual fit key uses text width, text height and text depth to set the dimensions of the new node (which also creates the problem that the new node can’t be thinner than the fitted node). Of course, now the inner seps are still active and the minimum height is only that, minimum (meaning that if you write more in it than it can take it will stretch where as the original fit key wasn’t really suited for putting text in the created node anyway).

Code

\documentclass[tikz,convert=false]{standalone}
\usetikzlibrary{fit,positioning,shapes}
\makeatletter
\def\pgfutil@firstofmany#1#2\pgf@stop{#1}
\def\pgfutil@secondofmany#1#2\pgf@stop{#2}
\def\tikz@lib@place@of@#1#2#3{%
  \def\pgf@tempa{fit bounding box}%
  \edef\pgf@temp{\expandafter\pgfutil@firstofmany#2\pgf@stop}
  \if\pgf@temp(%
    \tikz@lib@place@fit@scan{#2}{0}%
  \else\if\pgf@temp|
      \expandafter\tikz@lib@place@fit@scan\expandafter{\pgfutil@secondofmany#2\pgf@stop}{1}%
    \else\ifx\pgf@temp\tikz@activebar
        \expandafter\tikz@lib@place@fit@scan\expandafter{\pgfutil@secondofmany#2\pgf@stop}{1}%
      \else\if\pgf@temp-
          \expandafter\tikz@lib@place@fit@scan\expandafter{\pgfutil@secondofmany#2\pgf@stop}{2}%
        \else\if\pgf@temp+
            \expandafter\tikz@lib@place@fit@scan\expandafter{\pgfutil@secondofmany#2\pgf@stop}{3}%
          \else
            \def\pgf@tempa{#2}%
          \fi
        \fi
      \fi
    \fi
  \fi
  \expandafter\tikz@scan@one@point\expandafter\tikz@lib@place@remember\expandafter(\pgf@tempa)%
  \iftikz@shapeborder%
    % Ok, this is relative to a border.
    \iftikz@lib@ignore@size%
      \edef\tikz@node@at{\noexpand\pgfpointanchor{\tikz@shapeborder@name}{center}}%
      \def\tikz@anchor{center}%
    \else%
      \edef\tikz@node@at{\noexpand\pgfpointanchor{\tikz@shapeborder@name}{#3}}%
    \fi%
  \fi%
  \edef\tikz@lib@place@nums{#1}%
}
\def\tikz@lib@place@fit@scan#1#2{
  \pgf@xb=-16000pt\relax%
  \pgf@xa=16000pt\relax%
  \pgf@yb=-16000pt\relax%
  \pgf@ya=16000pt\relax%
  \if\pgfutil@firstofmany#1\pgf@stop(%
    \tikz@lib@fit@scan#1\pgf@stop%
  \else
    \tikz@lib@fit@scan(#1)\pgf@stop
  \fi
  \ifdim\pgf@xa>\pgf@xa
    % shouldn't happen
  \else
     \expandafter\def\csname pgf@sh@ns@fit bounding box\endcsname{rectangle}%
     \expandafter\edef\csname pgf@sh@np@fit bounding box\endcsname{%
       \def\noexpand\southwest{\noexpand\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}%
       \def\noexpand\northeast{\noexpand\pgfqpoint{\the\pgf@xb}{\the\pgf@yb}}%
     }%
     \expandafter\def\csname pgf@sh@nt@fit bounding box\endcsname{{1}{0}{0}{1}{0pt}{0pt}}%
     \expandafter\def\csname pgf@sh@pi@fit bounding box\endcsname{\pgfpictureid}%
     \ifcase#2\relax
     \or % 1 = vertical
       \pgf@y=\pgf@yb%
       \advance\pgf@y by-\pgf@ya%
       \edef\pgf@marshal{\noexpand\tikzset{minimum height={\the\pgf@y-2*(\noexpand\pgfkeysvalueof{/pgf/outer ysep})}}}%
       \pgf@marshal
     \or % 2 = horizontal
       \pgf@x=\pgf@xb%
       \advance\pgf@x by-\pgf@xa%
       \edef\pgf@marshal{\noexpand\tikzset{minimum width={\the\pgf@x-2*(\noexpand\pgfkeysvalueof{/pgf/outer xsep})}}}%
       \pgf@marshal
     \or % 3 = both directions
       \pgf@y=\pgf@yb%
       \advance\pgf@y by-\pgf@ya%
       \pgf@x=\pgf@xb%
       \advance\pgf@x by-\pgf@xa%
       \edef\pgf@marshal{\noexpand\tikzset{minimum height={\the\pgf@y-2*(\noexpand\pgfkeysvalueof{/pgf/outer ysep})},minimum width={\the\pgf@x-2*(\noexpand\pgfkeysvalueof{/pgf/outer xsep})}}}%
       \pgf@marshal
     \fi
  \fi
}
\tikzset{
  fit bounding box/.code={\tikz@lib@place@fit@scan{#1}{0}},
  span vertical/.code={\tikz@lib@place@fit@scan{#1}{1}},
  span horizontal/.code={\tikz@lib@place@fit@scan{#1}{2}},
  span/.code={\tikz@lib@place@fit@scan{#1}{3}}}

\makeatother
\begin{document}
\begin{tikzpicture}[node distance=5pt,every node/.style={draw,minimum width=50pt}]
  \node(A)             {A};
  \node(B)[below=of A, xshift=.5cm] {B};
  \node(C)[below=of B, xshift=-1cm] {C};
  \node(D)[below=of C, xshift=.75cm] {D};

  \node[right=of |(A)(B)(D)]          {Z};

  \node[left=of |(A)(B), ultra thick] {Y};
  \node[left=of |(C)(D), minimum width=75pt] (X) {X};

  \node[span vertical=(A)(B), above=of -X] {y};

  \node[below=of -(A)(B)(C)(D)] {0};
\end{tikzpicture}

\begin{tikzpicture}[node distance=5pt,every node/.style={draw, ellipse, minimum width=25pt}]
  \node(A)             {A};
  \node(B)[below=of A, xshift=.5cm] {B};
  \node(C)[below=of B, xshift=-1cm] {C};
  \node(D)[below=of C, xshift=.75cm] {D};

  \node[right=of |(A)(B)(D)]          {Z};

  \node[left=of |(A)(B), ultra thick] {Y};
  \node[left=of |(C)(D), minimum width=50pt]          (X) {X};

  \node[span vertical=(A)(B), above=of -X] {y};

  \node[below=of -(A)(B)(C)(D)] {0};
\end{tikzpicture}
\end{document}

Output

enter image description here


enter image description here