24

I have the two PGF nodes foo and bar positioned in a row.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{positioning}
\begin{document}
  \begin{tikzpicture}[node distance=0.2cm,mynode/.style={rectangle,draw}]
    \node[mynode] (foo)                {foo};
    \node[mynode] (bar) [right=of foo] {bar};
  \end{tikzpicture}
\end{document}

How can I create a third node below those two nodes with the width of foo, bar and the node distance between them? Is there a way to do this with the fit library or is this the wrong approach?

1

5 Answers 5

26

You can use yshift together with fit and inner sep=0pt to get a node of the same height and width as the other nodes, but shifted vertically. Note that the placement of the node text is different than in a normal node, so I would suggest you use the label=center:<text> option to place the text instead. As Martin points out, you should also set the outer sep of the nodes you're fitting around to 0pt, as otherwise your new node will be too large by \pgflinewidth.

tikz fit and shift

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{positioning,fit}
\begin{document}
  \begin{tikzpicture}[node distance=0.2cm,mynode/.style={outer sep=0pt, draw}]
    \node[mynode] (foo)                {foo};
    \node[mynode] (bar) [right=of foo] {bar};
    \node [
        mynode,
        inner sep=0pt,
        yshift=-1cm,
        fit={(foo) (bar)},
        label=center:foobar] {};
  \end{tikzpicture}
\end{document}

Below is an approach to get the vertical spacing between the old nodes and the newly created one right. Using the calc library, you can shift the new node down by the height of the old nodes by using ($(foo.south) - (foo.north)$) You can't directly read the value of node distance, so I've appended code to store the value in a new key that can be read in a yshift.

fitted and automatically shifted node

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

\pgfkeys{
    /tikz/node distance/.append code={
        \pgfkeyssetvalue{/tikz/node distance value}{#1}
    }
}

\begin{document}
  \begin{tikzpicture}[
    node distance=0.2cm,
    mynode/.style={
        draw,
        outer sep=0pt
    }]
    \node[mynode] (foo)                {foo};
    \node[mynode] (bar) [right=of foo] {bar};
    \node [
        mynode,
        inner sep=0pt,
        shift=($(foo.south)-(foo.north)$),
        yshift=-\pgfkeysvalueof{/tikz/node distance value},
        fit={(foo) (bar)},
        label=center:foobar] {};
  \end{tikzpicture}
\end{document}

A different approach is using the let syntax to calculate the difference between bar.east and foo.west, and using that to set the minimum width of the new node:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{positioning,calc}
\begin{document}
  \begin{tikzpicture}[node distance=0.2cm,mynode/.style={rectangle,draw}]
    \node[mynode] (foo)                {foo};
    \node[mynode] (bar) [right=of foo] {bar};
    \path let
        \p1=(foo.west),
        \p2=(bar.east)
    in node [
        mynode,
        below=of foo.south west,
        anchor=north west,
        minimum width=\x2-\x1-\pgflinewidth
    ] {foobar};
  \end{tikzpicture}
\end{document}
8
  • The inner sep was what I didn't know about yet. If someone knew a way to calculate the yshift automatically I'd be interested to hear it.
    – h0b0
    Jul 19, 2011 at 12:20
  • 1
    The outer sep of the first two nodes should also create issues. It places the anchors on the outside of the node border, not in the middle of it. This should cause the fitted node to be 2x.5\pgflinewidth wider. Jul 19, 2011 at 12:35
  • 1
    @h0b0: I assume you want the same gap between the old and the new node as you have between the two old ones? I've edited my answer to show one possible way of doing this.
    – Jake
    Jul 19, 2011 at 12:58
  • @Jake: I suffered from that dearly. The issue is also that you can't get the original outer sep of the referenced nodes afterwards. Jul 19, 2011 at 12:59
  • @Jake: Thanks for the edit, I remember this kind of calculation. Do you think one could construct something like a \xcoordof macro that would allow inline usage, avoiding the let command? If you like, I can open a separate question for this.
    – krlmlr
    Jul 18, 2012 at 13:25
10
+25

Update

Without fit and calc

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

  \makeatletter  
  \tikzset{minimum dist/.style 2 args={%
    insert path={% 
      \pgfextra{% 
       \path (#1);
       \pgfgetlastxy{\xa}{\ya} 
        \path (#2);
       \pgfgetlastxy{\xb}{\yb}   
       \pgfpointdiff{\pgfpoint{\xa}{\ya}}%
                     {\pgfpoint{\xb}{\yb}}%
       \pgf@xa=\pgf@x}
        },
    minimum width=\pgf@xa}
    } 

  \begin{document}
    \begin{tikzpicture}[node distance=0.2cm,mynode/.style={rectangle,draw}]
      \node[mynode] (foo)                {foo};
      \node[mynode] (bar) [right=of foo] {bar};
      \node [mynode,below= 1cm of foo.south west,
            anchor=west,
            minimum dist={foo.south west}{bar.north east} 
           ] {foobar}; 
    \end{tikzpicture}
  \end{document}  

Another variant with fit:

 \documentclass{article}
 \usepackage{tikz}
 \usetikzlibrary{positioning,fit,calc}
 \begin{document}
   \begin{tikzpicture}[node distance=0.2cm,mynode/.style={rectangle,draw}]
     \node[mynode] (foo)                {foo};
     \node[mynode] (bar) [right=of foo] {bar};
     \node [mynode,below=1cm of foo.south west,inner sep=0pt,
           anchor=west,
           fit={($(foo.south west)+(.5*\pgflinewidth,0)$) 
                             ($(bar.north east)-(.5*\pgflinewidth,0)$)},
           label=center:foobar] {}; 
   \end{tikzpicture}
 \end{document}      

enter image description here

6
  • If you fit using the anchor nodes, why not move the anchor nodes instead of yshifting?
    – krlmlr
    Jul 18, 2012 at 17:24
  • What is your idea? What you want to say by "moving" the anchor nodes ? Jul 18, 2012 at 18:22
  • I mean adding a vertical shift to the nodes used for fit instead of yshift-ing the node.
    – krlmlr
    Jul 18, 2012 at 21:02
  • Ah! Finally I understand your request (perhaps , I'm not sure) below=1cm for example. Personally I don't use positioning and I prefer yshift-ing ! because it's more easy to scale the picture. Jul 19, 2012 at 7:47
  • Do you want to substitute the first solution with the two-argument variant from the linked question?
    – krlmlr
    Jul 19, 2012 at 18:04
5

A variant using fit library but without outer sep=0pt:

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{positioning,fit}
\begin{document}
  \begin{tikzpicture}[node distance=0.2cm,mynode/.style={rectangle,draw,line width=2pt}]
    \node[mynode] (foo)                {foo};
    \node[mynode] (bar) [right=of foo] {bar};

    \node[fit=(foo)(bar),yshift=-1cm,%
    line width=1pt, %
    inner sep=-.5\pgflinewidth, % -1/2 of current line width
    draw,label=center:foobar]{};
  \end{tikzpicture}
\end{document}
0
5

Another one with a getdist=p1 and p2 syntax. It gets the left border of the first and right of the second. I'm not sure if this is simpler but slightly cleaner.

\documentclass[border=3mm]{standalone}
\usepackage{tikz}

\makeatletter
\tikzset{
    getdist/.style args={#1 and #2}{
    getdistc={#1}{#2},minimum width=\mylength-\pgflinewidth
    },
getdistc/.code 2 args={
\pgfextra{
    \pgfpointdiff{\pgfpointanchor{#1}{west}}{\pgfpointanchor{#2}{east}}
    \xdef\mylength{\the\pgf@x}
         }
    }
}
\makeatother

\begin{document}
\begin{tikzpicture}
\node[ultra thick][draw] (f) {foo};
\node[draw,ultra thin] at (1cm,0.5cm) (b) {bar};

\node[anchor=west,getdist=f and b,draw] at ([yshift=-6mm]f.west) {foobar};

\end{tikzpicture}
\end{document}

enter image description here

2
  • With .code 2 args (fine idea !), you don't need \pgfextra. Jul 24, 2012 at 6:58
  • @Altermundus Ah, that's from an earlier version where I did all of this inside a path. Indeed it's not needed.
    – percusse
    Jul 24, 2012 at 7:58
4

Another solution, which does not involve the fit library, but computes instead the required width of the node:

\documentclass{article}
\usepackage{tikz}
\begin{document}
\usetikzlibrary{positioning,calc}
  \begin{tikzpicture}[node distance=0.2cm,mynode/.style={rectangle,draw}]
    \node[mynode] (foo)                {foo};
    \node[mynode] (bar) [right=of foo] {bar};
    \path let \p1=($(foo.west)-(bar.east)$),
              \n1 = {veclen(\p1)-0.4pt}      % 0.4pt is the width of the border line
              in node[mynode, below=of foo.south west, anchor=north west,
                      minimum width=\n1] {foobar};
  \end{tikzpicture}  
\end{document}

Resulting in:

Result

3
  • Nice. Is there a way to programmatically retrieve the value of the border line width (0.4pt)?
    – krlmlr
    Jul 18, 2012 at 22:38
  • Oh, I just noticed that Jake's answer also containes this solution, but even better, because he uses \pgflinewidth instead of the hardcoded value 0.4pt. Shoud I retire my answer?
    – JLDiaz
    Jul 18, 2012 at 23:01
  • Indeed, but your code is shorter.
    – krlmlr
    Jul 18, 2012 at 23:03

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.