4

I would like to achieve the following:

what I want

Namely a top left node, a bottom left node with the same width as the top left node (with possibly different height), and a right node with the total height of two left nodes. The right node must contain multiple things, possibly pictures.

The following code solves this:

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

\begin{document}

\newdimen\XCoordA
\newdimen\YCoordA
\newdimen\XCoordB
\newdimen\YCoordB

\newcommand*{\ExtractCoordinateA}[1]{\path (#1); \pgfgetlastxy{\XCoordA}{\YCoordA};}%
\newcommand*{\ExtractCoordinateB}[1]{\path (#1); \pgfgetlastxy{\XCoordB}{\YCoordB};}%

\pgfdeclarelayer{toplayer}%
\pgfdeclarelayer{background}%
\pgfsetlayers{background,toplayer}%
\begin{tikzpicture}[
    block/.style=               {inner sep=10pt, font=\footnotesize, align=center, draw, thick},
    left_block/.style=          {block, minimum width=100pt},
    zerofit_block/.style=       {block, inner sep=0pt},
    inner_text/.style=          {inner sep=0pt, font=\small, align=center},
    inner_text_small/.style=    {inner_text, font=\tiny},
]

\begin{pgfonlayer}{background}

    \node [left_block, fill=green!50!white]
        (block_top_left) {This is\\block\_top\_left};
    \node [left_block, fill=red!50!white, below=0.2 of block_top_left.south west, anchor=north west]
        (block_bottom_left) {block\_bottom\_left};

\end{pgfonlayer}

\begin{pgfonlayer}{toplayer}

    \node [inner_text, below right=-0.7 and 1 of block_top_left]
        (block_right_label) {This is\\block\_right};
    \node [inner_text_small, below=0.1 of block_right_label]
        (block_right_text) {This is\\block\_right's\\text};

\end{pgfonlayer}

\begin{pgfonlayer}{background}

    \ExtractCoordinateA{$(block_top_left.north) - (0,0.4pt)$}; %0.4pt is half of the thick line width
    \ExtractCoordinateB{$(block_right_label)$};
    \coordinate (block_right_north) at (\XCoordB,\YCoordA);
    \ExtractCoordinateA{$(block_bottom_left.south) + (0,0.4pt)$};
    \coordinate (block_right_south) at (\XCoordB,\YCoordA);
    \coordinate (block_right_east_west_inner_sep) at (0.5,0);
    \coordinate (block_right_west) at ($(block_right_label.west) - (block_right_east_west_inner_sep)$);
    \coordinate (block_right_east) at ($(block_right_label.east) + (block_right_east_west_inner_sep)$);

    \node [zerofit_block, fill=yellow!50!white, fit=(block_right_north) (block_right_south) (block_right_west) (block_right_east)]
        (block_right) {};

\end{pgfonlayer}

\end{tikzpicture}%
\end{document}

There are numerous ugly things about this solution that I don't like:

  1. Width of the two left nodes are controlled manually, with minimum width, but I would like to have the wider node have just an inner sep and the narrower node stretch in width to follow it.
  2. Controlling the fitted right node's north, south, east and west with four manually calculated coordinates seems like overkill and just feels wrong.
  3. North and south artificial coordinate of the fitted right node require that the drawn line thickness is subtracted. This is not even manually calculated but hardcoded to half the draw thickness; as soon as the draw thickness of the nodes change, this must be adjusted by hand.
  4. The worst problem: Right block's inner blocks (block_right_label and block_right_text) are manually positioned. It would be better to somehow control the fitted node edges and draw the inner nodes after the fitted node has been positioned. This way, I could precisely control the spacing between the right fitted node and the left nodes, maybe even set it equal to the spacing between the left nodes. Not to mention that manually positioning the inner nodes will not achieve a perfectly equal top and bottom inner sep like this.

Am I approaching this problem completely wrong? Fit seems like the wrong solution here. In any case, can you offer me solutions for any number of the above problems?

2 Answers 2

5
  1. This is done by setting the width of the first node, and then fitting the other two nodes to the previous nodes, using the fit library.
  2. Same as #1.
  3. Solved with #1.
  4. This depends on what you want to do. If you only want two short nodes inside of the yellow, you can add two more nodes anchored there. But it depends on what you want to do

Output

figure 1

Code

\documentclass[margin=10pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{positioning,fit}

\tikzset{
    every node/.style={draw, rectangle, align=center, text width=3cm, inner sep=0, thick, outer sep=0}
}

\begin{document}
\begin{tikzpicture}[node distance=5mm]
\node[fill=green!40, minimum height=2cm, minimum width=5cm, text width=3cm] (one) {This is\\block top left};
\node[fill=red!40,  fit={(one.west) (one.east)}, minimum height=1cm, anchor=north west, below =of one] (two) {block bottom left};
\node[fill=yellow, fit={(one)(two)},right =of two.south east, anchor=south west] (three) {This is\\block right};

\end{tikzpicture}
\end{document}
6
  • I didn't know east west north and south could be used inside fit, thanks! Is it possible to combine it with inner sep (minimum height seems to do the job for now)? Also, I'm getting the same issue with your proposed bottom left block; the text doesn't seem to be vertically centered but offset to the north. This is also true for multiline texts. What could be the issue? Jul 11, 2015 at 11:52
  • It almost looks like instead of the text's vertical center, its bottom line is vertically centered inside the node. Jul 11, 2015 at 12:03
  • @AyberkÖzgür Yeah that offset is odd. I'm trying to understand what is going on, I'm sure I'm missing something very obvious. :D Inner and outer sep must be 0 for it to be easier to fit the nodes, if you remove inner sep=0, and then compile, you'll see the difference. Although I'm not sure what you mean there when you say combine with inner sep.
    – Alenanno
    Jul 11, 2015 at 12:09
  • Yes, it absolutely doesn't like inner sep larger than zero :) What I mean is that it would be nice to say e.g inner sep=5pt while fitting east and west to basically say "I want to fit east and west but the remaining edges should follow the inner sep rule, namely north and south". This is actually a feature I would very much like, namely different inner seps for west, east, north and south. Jul 11, 2015 at 12:10
  • @AyberkÖzgür That can be done using inner xsep and inner ysep, but it for works for both sides.
    – Alenanno
    Jul 11, 2015 at 12:31
2

Here's another approach using the calc library and the let syntax.

First the widths for the two nodes to the left is measured (the nodes are not typeset yet at this point) and the maximum is chosen to be the common width (there's no need here to predefine the width for the first node, as in the other answer); then the nodes are typeset one below the other with the chosen width and finally the node to the right is typeset with the appropriate height.

enter image description here

The code:

\documentclass[margin=10pt,varwidth]{standalone}
\usepackage{tikz}
\usetikzlibrary{positioning,calc}

\tikzset{
every node/.style={
  draw, 
  rectangle, 
  align=center, 
  thick,
  outer sep=0
  }
}

\begin{document}

\begin{tikzpicture}[node distance=5mm]
\def\NodeOneText{Shorter}
\def\NodeTwoText{Longer text \\ spanning three lines \\ for the exmaple}
\begin{pgfinterruptboundingbox}
\node[draw=none,opacity=0,name=one,node contents={\NodeOneText}] ;
\node[draw=none,opacity=0,name=two,node contents={\NodeTwoText}] ;
\end{pgfinterruptboundingbox}
\path
  let 
  \p1=(one.west),
  \p2=(one.east),
  \p3=(two.west),
  \p4=(two.east) in
  node[
    fill=orange!40,
    anchor=north west,
    text width={max(\x2-\x1,\x4-\x3)-0.6666em}
    ] 
    (one) {\NodeOneText}
  node[
    fill=olive!60, 
    below=of one,
    text width={max(\x2-\x1,\x4-\x3)-0.6666em}
    ] 
    (two) {\NodeTwoText};
\path
  let 
  \p1=(one.north),
  \p2=(two.south) in
  node[
    fill=magenta!70, 
    right =of two.south east,
    anchor=south west,
    minimum height=\y1-\y2,
    ] 
    (three) {This is \\ the block \\ to the right};
\end{tikzpicture}\par\bigskip

\begin{tikzpicture}[node distance=5mm]
\def\NodeOneText{This is some test text spanning \\ two lines}
\def\NodeTwoText{Another text}
\begin{pgfinterruptboundingbox}
\node[draw=none,opacity=0,name=one,node contents={\NodeOneText}] ;
\node[draw=none,opacity=0,name=two,node contents={\NodeTwoText}] ;
\end{pgfinterruptboundingbox}
\path
  let 
  \p1=(one.west),
  \p2=(one.east),
  \p3=(two.west),
  \p4=(two.east) in
  node[
    fill=green!40,
    anchor=north west,
    text width={max(\x2-\x1,\x4-\x3)-0.6666em}
    ] 
    (one) {\NodeOneText}
  node[
    fill=red!50, 
    below=of one,
    text width={max(\x2-\x1,\x4-\x3)-0.6666em}
    ] 
    (two) {\NodeTwoText};
\path
  let 
  \p1=(one.north),
  \p2=(two.south) in
  node[
    fill=yellow, 
    right =of two.south east,
    anchor=south west,
    minimum height=\y1-\y2,
    ] 
    (three) {This is \\ the block \\ to the right};
\end{tikzpicture}\par\bigskip

\begin{tikzpicture}[node distance=5mm]
\def\NodeOneText{A}
\def\NodeTwoText{B}
\begin{pgfinterruptboundingbox}
\node[draw=none,opacity=0,name=one,node contents={\NodeOneText}] ;
\node[draw=none,opacity=0,name=two,node contents={\NodeTwoText}] ;
\end{pgfinterruptboundingbox}
\path
  let 
  \p1=(one.west),
  \p2=(one.east),
  \p3=(two.west),
  \p4=(two.east) in
  node[
    fill=blue!40,
    anchor=north west,
    text width={max(\x2-\x1,\x4-\x3)-0.6666em}
    ] 
    (one) {\NodeOneText}
  node[
    fill=gray!50, 
    below=of one,
    text width={max(\x2-\x1,\x4-\x3)-0.6666em}
    ] 
    (two) {\NodeTwoText};
\path
  let 
  \p1=(one.north),
  \p2=(two.south) in
  node[
    fill=pink, 
    right =of two.south east,
    anchor=south west,
    minimum height=\y1-\y2,
    ] 
    (three) {This is \\ the block \\ to the right};
\end{tikzpicture}

\end{document}

You must log in to answer this question.

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