8

I'd like to have Tikz automatically draw a "bounding box" around some nodes, which can have an arbitrary number of "ports" on the left and right sides that should be given anchors, so that edges of the contained nodes can connect to the ports on the boundaries.

An example: Example

I'd like the boundary ports to be evenly spaced automatically, and don't want to have to specify them one-by-one!

For example, I'd like to somehow say something like this:

\documentclass[tikz]{standalone}
\begin{document}
\begin{tikzpicture} [node distance=2cm]
    \tikzstyle{foo}=[draw,circle,inner sep=0.4cm]
    \tikzstyle{arr}=[->, >=latex, shorten >=1pt, semithick]
    \node[foo] (a) {};
    \node[foo] (b) [right of=a] {};
    \node[fill] (l1) [above left of=a] {}; % I don't want to specify these 3!
    \node[fill] (l2) [below left of=a] {};
    \node[fill] (r1) [right of=b] {};
    \draw (a.east) edge[arr] (b.west);
    \draw (l1) edge[arr] (a.west); % But I would like l1, l2, r1 to exist!
    \draw (l2) edge[arr] (a.west);
    \draw (b.east) edge[arr] (r1);
\end{tikzpicture}
\end{document}

But without manually specifying l1,l2,r1 (I'd rather pass parameters of 2/1 to say 2 ports on the left and 1 on the right). Also, I don't know how do get the surrounding rectangle to be drawn automatically (with some "padding" between the nodes and the rectangle).

EDIT:

Kevin C's answer is very nice, but it seems the bounding box takes account of labels (I'd rather it didn't), and asking for 0 boundary ports, spuriously adds one at the top and bottom of the edge. E.g.

\begin{document}
\begin{tikzpicture}
    \tikzset{
        foo/.style={draw,circle,inner sep=0.4cm},
        arr/.style={->, >=latex, shorten >=1pt, semithick},
        }
    \node[foo] (a) [label=above:$a$]{};

    \anchorbound{0}{1}

    \draw[arr] (a.east) to node[above] {$\alpha$}  (r1);
\end{tikzpicture}
\end{document}

Looks a bit strange (the a node is relatively lower than I'd like). Can I somehow tell tikz to ignore the labels in the bounding box calculation?

enter image description here


  • There are a few details missing. 1) Is your node a and b combined? 2) If there are 3 ports requested, how should they be placed what should be the distance etc. 3) Should the bounding box start from the first port, how much padding is included in your figure? There are more questions but these at least would make the question better formulated. – percusse Aug 22 '13 at 12:09
  • Another question is important (at least at my eyes): how many ports you would consider per node? – Claudio Fiandrino Aug 22 '13 at 12:15
  • 2
    You can try \node[foo] (a) [label={[overlay]above:$a$}] {}; so that the label does not affect the bounding box. – Qrrbrbirlbel Aug 23 '13 at 1:32
6

Updated

The definition of \anchorbound command is updated to take into account that 0 may be a value of the two arguments. Basically, if an argument's value is 0, then no action is taken.

\newcommand\anchorbound[2]{
    % draw bounding box 
    \draw($(current bounding box.north west)+(-1,1)$)rectangle($(current bounding box.south east)+(1,-1)$);
    % coordinates of bounding box 
    \coordinate(nw)at(current bounding box.north west);
    \coordinate(ne)at(current bounding box.north east);
    \coordinate(sw)at(current bounding box.south west);
    \coordinate(se)at(current bounding box.south east);
    % specifying "ports"
    \ifnum#1=0,{},
    \else
      \foreach \l in {1,...,#1} {
        \pgfmathparse{\l/(#1+1)}
        \node(l\l)[fill]at($(nw)!\pgfmathresult!(sw)$){};
      }
    \fi
    \ifnum#2=0,{},
    \else
      \foreach \r in {1,...,#2}{
        \pgfmathparse{\r/(#2+1)}
        \node(r\r)[fill]at($(ne)!\pgfmathresult!(se)$){};
      }
    \fi
}

The \anchorbound command takes two arguments; the first specifies how many ports on the left border, the second specifies the number of ports on the right border.

You should use it after you've drawn all the contents you'd want to be "boxed". Then \anchorbound does the following:

  1. it draws a bounding box with 1cm padding on all sides
  2. it marks down the coordinates of the four corners of the bounding box
  3. depending on the number of ports you specify for each side, it places filled nodes evenly on the left/right border

Afterwards, you can draw arrows connecting "ports" to other nodes.

Full Code:

\documentclass[tikz,border=2pt]{standalone}
\usetikzlibrary{calc}

\newcommand\anchorbound[2]{
    % draw bounding box 
    \draw($(current bounding box.north west)+(-1,1)$)rectangle($(current bounding box.south east)+(1,-1)$);
    % coordinates of bounding box 
    \coordinate(nw)at(current bounding box.north west);
    \coordinate(ne)at(current bounding box.north east);
    \coordinate(sw)at(current bounding box.south west);
    \coordinate(se)at(current bounding box.south east);
    % specifying "ports"
    \ifnum#1=0,{},
    \else
      \foreach \l in {1,...,#1} {
        \pgfmathparse{\l/(#1+1)}
        \node(l\l)[fill]at($(nw)!\pgfmathresult!(sw)$){};
      }
    \fi
    \ifnum#2=0,{},
    \else
      \foreach \r in {1,...,#2}{
        \pgfmathparse{\r/(#2+1)}
        \node(r\r)[fill]at($(ne)!\pgfmathresult!(se)$){};
      }
    \fi
}

\begin{document}
\begin{tikzpicture} [node distance=2cm]
    \tikzset{
        foo/.style={draw,circle,inner sep=0.4cm},
        arr/.style={->, >=latex, shorten >=1pt, semithick},
        }
    \node[foo] (a) {};
    \node[foo] (b) [right of=a] {};

    \anchorbound{2}{1}

    \draw (a.east) edge[arr] (b.west);
    \draw (l1) edge[arr] (a.west); % But I would like l1, l2, r1 to exist!
    \draw (l2) edge[arr] (a.west);
    \draw (b.east) edge[arr] (r1);
\end{tikzpicture}
\end{document}

enter image description here


By the way, I also changed your \tikzstyle syntax, which is obsolete, to the \tikzset syntax.


Output of updated example:

enter image description here

  • Nice, that looks very nearly there! Just one thing - if I use e.g. \anchorbound{0}{1} I get two spurious "ports" on the left boundary. I presume it's something to do with the foreach incorrectly interpreting the range {1..0}, but I'm not well-versed enough in Latex to fix it! Thanks for the tip re: tikzstyle, I hadn't realised it is obsolete. – owst Aug 22 '13 at 17:19
  • I've added an edit to the original question, for a further tweak request. – owst Aug 22 '13 at 17:39
  • @owst: see my updated answer. To tell TikZ to "ignore" the space created by the label, please follow Qrrbrbirlbel's comment and use the overlay key. – Herr K. Aug 23 '13 at 8:02
6

A combination of fit (for the box around it) and calc (for the placement of the ports).

One could also think of a new shape like the circuits with their inputs and output anchors.

One can add new functions to place the ports by adding more choices to place ports function or by simply redefining \tikzPlacePorts yourself.

One could also implement the function by means of the /pgf/declare function key (different syntax, works better with PGF math).

Code B shows an implementation via PGFkeys and the usage of do ports directly on the node that shall recieve the ports.

Code A

\documentclass[tikz,convert=false]{standalone}
\usetikzlibrary{positioning, fit, calc}
\providecommand*{\tikzPlacePorts}{}
\tikzset{
  place ports/.code args={#1:#2:(#3)}{
    \ifcase#1 % east
      \def\anchorA{north east}%
      \def\anchorB{south east}%
      \def\direc{e}%
    \or % north
      \def\anchorA{north west}%
      \def\anchorB{north east}%
      \def\direc{n}%
    \or % west
      \def\anchorA{north west}%
      \def\anchorB{south west}%
      \def\direc{w}%
    \else % south
      \def\anchorA{south west}%
      \def\anchorB{south east}%
      \def\direc{s}%
    \fi
    \tikzset{
      @place ports/.ecode={\noexpand\node[port] at ($(#3.\anchorA)!\noexpand\tikzPlacePorts{##1}{#2}!(#3.\anchorB)$) (#3-\direc-##1) {};},
      @place ports/.list={1,...,#2}
    }
  },
  place ports function/.is choice,
  place ports function/on corners/.code    =\def\tikzPlacePorts##1##2{(##1-1)/(##2-1)},
  place ports function/not on corners/.code=\def\tikzPlacePorts##1##2{(##1)/(##2+1)},
  place ports function=not on corners
}
\tikzset{
  foo/.style={shape=circle,draw, inner sep=+0pt, minimum size=+.8cm},
  arr/.style={->, >=latex, shorten >=1pt, semithick},
  port/.style={shape=rectangle, draw, fill, inner sep=+0pt, minimum size=+1mm}}
\begin{document}
\begin{tikzpicture}
  \node[foo]             (a) {};
  \node[foo, right=of a] (b) {};

  \node[draw, fit=(a)(b), inner xsep=.5cm, inner ysep=.8cm, outer sep=+0pt] (ab) {};

  \tikzset{place ports=2:2:(ab)}
  \tikzset{place ports=0:1:(ab)}

  \path[arr] (ab-w-1) edge (a.west)
             (ab-w-2) edge (a.west)
             (a)      edge (b)
             (b)      edge (ab-e-1);
\end{tikzpicture}
\begin{tikzpicture}
  \node[foo, label=$a$] (a) {};

  \node[draw, fit=(a), inner sep=.8cm, outer sep=+0pt] (a') {};

  \tikzset{place ports function=on corners, place ports=2:2:(a')}
  \tikzset{place ports function=not on corners, place ports=0:1:(a')}

  \path[arr] (a'-w-1) edge (a)
             (a'-w-2) edge (a)
             (a)      edge node[above] {$\alpha$} (a'-e-1);
\end{tikzpicture}
\end{document}

Code B

\documentclass[tikz,convert=false]{standalone}
\makeatletter
\pgfkeys{/handlers/.pgfmath/.code=\pgfmathparse{#1}\expandafter\pgfkeys@exp@call\expandafter{\pgfmathresult}}
\makeatother
\usetikzlibrary{positioning, fit}
\tikzset{
  ports/.code={\pgfqkeys{/tikz/ports}{#1}},
  do ports/.code={\pgfqkeys{/tikz/ports}{#1, do}},
  ports/.cd,
  rect east/.code={%
    \def\tikzPlacePortsName{east}%
    \def\tikzPlacePortsStart##1{##1.north east}%
    \def\tikzPlacePortsTarget##1{##1.south east}},
  rect west/.code={%
    \def\tikzPlacePortsName{west}%
    \def\tikzPlacePortsStart##1{##1.north west}%
    \def\tikzPlacePortsTarget##1{##1.south west}},
  function/.is choice,
  function/not on corners/.code={%
    \def\tikzPlacePorts##1##2{pos/.pgfmath={(##1)/(##2+1)}}},
  function/on corners/.code={%
    \def\tikzPlacePorts##1##2{pos/.pgfmath={(##1-1)/(##2-1)}}},
  function=not on corners,
  total/.initial=1,
  do/.style={
    place ports/.estyle={%
      /tikz/append after command={{%
        (\tikzPlacePortsStart{\noexpand\tikzlastnode})
          edge[draw=none] node[ports/port/.try, \tikzPlacePorts{##1}{#1}] (\noexpand\tikzlastnode-\tikzPlacePortsName-##1) {}
        (\tikzPlacePortsTarget{\noexpand\tikzlastnode})}}},
    place ports/.list={1,...,#1}
  },
  do/.default={\pgfkeysvalueof{/tikz/ports/total}}
}
\tikzset{
  foo/.style={shape=circle,draw, inner sep=+0pt, minimum size=+.8cm},
  arr/.style={->, >=latex, shorten >=1pt, semithick},
  ports/port/.style={shape=rectangle, draw, fill, inner sep=+0pt, minimum size=+1mm}}
\begin{document}
\begin{tikzpicture}
  \node[foo]             (a) {};
  \node[foo, right=of a] (b) {};

  \node[draw, fit=(a)(b), inner xsep=.5cm, inner ysep=.8cm, outer sep=+0pt,
    do ports={rect west, total=2}, do ports={rect east, total=1}
  ] (ab) {};

  \path[arr] (ab-west-1) edge (a.west)
             (ab-west-2) edge (a.west)
             (a)      edge (b)
             (b)      edge (ab-east-1);
\end{tikzpicture}
\begin{tikzpicture}
  \node[foo, label=$a$] (a) {};

  \node[draw, fit=(a), inner sep=.8cm, outer sep=+0pt,
    do ports={rect east, total=1}, do ports={rect west, function=on corners, total=2}
  ] (a') {};

  \path[arr] (a'-west-1) edge (a)
             (a'-west-2) edge (a)
             (a)      edge node[above] {$\alpha$} (a'-east-1);
\end{tikzpicture}
\end{document}

Output

enter image description here

enter image description here

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.