5

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.

6

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

  • A few things to note: 1. Don’t change the outer separators if you can’t predict the outcome. 2. I have already included the code from this answer in the positioning-plus library. 3. Do not use node names that start with +, - or |. – Qrrbrbirlbel Jul 18 '13 at 17:33
6

As Qrrbrbirlbel pointed out, the problem was that I switched \y1 and \y2.
In TikZ, “up” means an increasing coordinate, in contrast to CSS. The solution becomes:

\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={\y1-\y2-\pgflinewidth},
         right=of A.north east,anchor=north west] {Z};
\end{tikzpicture}
\end{document}

Note that we need to subtract the line width, and to specify the anchor for proper placement.


Claudio showed another way to solve the problem, with the fit library:

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

\begin{document}
\begin{tikzpicture}[node distance=5pt,
                    every node/.style={draw, minimum width=100pt, outer sep=0pt}]

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

  \node(Z)[fit=(A)(D),right=of A.north east,anchor=north west, inner sep=0] {Z};

\end{tikzpicture}

\end{document}

Note how I used outer sep=0pt here to solve the line width mismatch.


The result

nodes

  • With outer sep=0pt you set the anchors in the middle of the line not on its border which may not preferable if you want to connect the nodes later. A solution that is not dependent on outer sep would be: \path let \p1=($(A.north)-(D.south)$) in node(Z)[right=of A.north east,anchor=north west, inner sep=0, minimum height=\y1-\pgflinewidth] {Z}; This, of course, could be hidden behind another key similar to fit. – Qrrbrbirlbel Jul 17 '13 at 13:44
  • @Qrrbrbirlbel but then we end up with a minimum height solution, which didn't work for me and was the reason I started this question. If your solution does work, could I convince you to turn this comment into an answer (perhaps with the hiding key)? I still haven't accepted an answer ;-) – Ruben Verborgh Jul 17 '13 at 17:24
  • 1
    I should have read the question, I guess. ;) In your question, you end up with a negative minimum height. You need either abs() or switch the subtraction. – Qrrbrbirlbel Jul 17 '13 at 17:30
  • Oh no, did I really? I always forget that "up" is positive in TikZ (as opposed to CSS). I will clarify this! – Ruben Verborgh Jul 17 '13 at 21:24
  • 1
    Well, it is more a math vs CSS thing (which is probably derived from the usual reading direction of humans (downwards) while up more feels like s height thing which mostly increases if you go up). A minor correction (to my comment and now your answer): You actually need to subtract 2*(\pgfkeysvalueof{/pgf/outer ysep}) (which defaults to 2* .5\pgflinewidth). – Qrrbrbirlbel Jul 17 '13 at 21:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.