9

I want to document layered software architectures and protocol stacks using TikZ. Each layer has one or more elements, and there is no strict association between elements that are above each other. I tried this:

\documentclass[tikz]{standalone}
\begin{document} 
\begin{tikzpicture}
    \node (a1) [draw] { A1 };
    \node (a2) [draw,right of=a1] { A2gq };
    \node (a3) [draw,right of=a2] { A3 };
    \node (b1) [draw,below of=a1] { B1 };
    \node (b2) [draw,right of=b1] { B2 };
    \node (b3) [draw,right of=b2] { B3gq };
    \node (b4) [draw,right of=b3] { B4 };
    \node (c1) [draw,below of=b1] { C1 };
    \node (c2) [draw,right of=c1] { C2gq };
    \node (c3) [draw,right of=c2] { C3 };
\end{tikzpicture}
\end{document}

Which looks terrible - especially since the base lines of the nodes are not aligned (nodes containing g or q are higher):

TikZ diagram

I would like it to look like this:

Manually painted diagram

Without having to calculate or guess lots of coordinates manually, in order to not complicate modifying the contents. I especially would like the right borders of the boxes to be aligned in order to get a cleaner look. This would be achieved by expanding nodes in rows that are shorter than the longest one, like words in a "justified" (in opposition to "ragged-left") text. The diagram will be used in a "beamer" presentation. Is there an easy way to do that?

10

Here is a proposal following the end of section 5.1 of the pgfmanual. In addition the positioning library is loaded.

\documentclass[tikz]{standalone}
\usetikzlibrary{positioning}
\begin{document} 
\begin{tikzpicture}[every node/.append style={minimum height=7mm,
text depth=0.25ex,draw},node distance=9mm and 3mm]
    \node (a1)  { A1 };
    \node (a2) [right=of a1] { A2gq };
    \node (a3) [right=of a2] { A3 };
    \draw[red] (a1.base -| a1.west) -- (a3.base -| a3.east);
    \node (b1) [below of=a1] { B1 };
    \node (b2) [right=of b1] { B2 };
    \node (b3) [right=of b2] { B3gq };
    \node (b4) [right=of b3] { B4 };
    \draw[red] (b1.base -| b1.west) -- (b4.base -| b4.east);
    \node (c1) [below of=b1] { C1 };
    \node (c2) [right=of c1] { C2gq };
    \node (c3) [right=of c2] { C3 };
\end{tikzpicture}
\end{document}

enter image description here

The red lines are just for illustration and should be removed of course.

UPDATE: Make sure that the outer ends of the rows are aligned. EDIT: Unified the positioning syntax, big thanks to @sgmoye!

\documentclass[tikz]{standalone}
\usetikzlibrary{positioning,fit}
\begin{document} 
\begin{tikzpicture}[every node/.append style={minimum height=7mm,
text depth=0.25ex,draw},node distance=3mm and 3mm]
    \node (b1)  { B1 };
    \node (b2) [right=of b1] { B2 };
    \node (b3) [right=of b2] { B3gq };
    \node (b4) [right=of b3] { B4 };
    \node (a1) [above=of b1] { A1 };
    \node (f1) [inner sep=-\pgflinewidth*0.5pt,fit=(b2.west|-a1.north) (b3.east|-a1.south)]{};
    \node (a3) [above=of b4] { A3 };
    \path (a1) -- (a3) node[midway,draw=none] (a2) { A2gq };
    \node (c1) [below=of b1] { C1 };
    \node (f1) [inner sep=-\pgflinewidth*0.5pt,fit=(b2.west|-c1.north) (b3.east|-c1.south)]{};
    \node (c3) [below=of b4] { C3 };
    \path (c1) -- (c3) node[midway,draw=none] (c2) { C2gq };
\end{tikzpicture}
\end{document}

enter image description here

ADDENDUM: A not so fiddly solution. No packages. Just for fun.

\documentclass{article}
\begin{document}
\begin{minipage}{5cm}
\fbox{ A1 }\hfill\fbox{ A2gq }\hfill\fbox{ A3 }\\[4mm]
\fbox{ B1 }\hfill\fbox{ B2 }\hfill\fbox{ B2gq }\hfill\fbox{ B4 }\\[4mm]
\fbox{ C1 }\hfill\fbox{ C2gq }\hfill\fbox{ C3 }
\end{minipage}
\end{document}

enter image description here

ADDENDUM: A bit more seriously: if you wish to combine the nice feature of \hfill with TikZ, then you may want to have a look at this snippet.

\documentclass{article}
\usepackage{tikz}
\newcounter{tikzbox}
\newcommand{\tikzbox}[2][]{\stepcounter{tikzbox}
\tikz[remember picture]{\node[draw,minimum height=7mm,
text depth=0.25ex,#1](tikzbox-\thetikzbox){#2};}}
\begin{document}
\begin{minipage}{5cm}
\tikzbox{A1}\hfill\tikzbox{A2gq}\hfill\tikzbox{A3}\\[4mm]
\tikzbox[alias=pferd]{B1}\hfill\tikzbox[alias=hase]{B2}\hfill\tikzbox{B2gq}\hfill\tikzbox{B4}\\[4mm]
\tikzbox{C1}\hfill\tikzbox{C2gq}\hfill\tikzbox{C3}
\end{minipage}
\tikz[overlay,remember picture]{%
\draw[thin,red] (tikzbox-1.base) -- (tikzbox-3.base);
\draw[thick,-latex] (pferd) -- (hase);
}
\end{document}

enter image description here

The red line is just for illustration, and there is some more serious application as well. The nodes get auto-labeled, but you can give them your own names using alias.

  • Nice! Wish I'd thought of that! – sgmoye Aug 22 '18 at 13:03
  • Looks a little fiddly. I had hoped that I was just missing something and there would actually be an easy way for that. Well then, thank you! – Erlkoenig Aug 22 '18 at 13:30
  • @marmot Just a note: nodes c1 and c3 are using the old of= syntax. Was that intentional? Otherwise, I'm lost in admiration. – sgmoye Aug 22 '18 at 13:56
  • @sgmoye No, it was not. Good catch! Thanks! (I actually agree with the OP that it is fiddly. However, IMHO it is also due to the fact the that the desired output is a bit less common such that the usual tricks like matrix or chains do not apply. The simplest solution would be to use \fboxes and \hfills inside a minipage, I think, but this is a TikZ question, and I do not know if the nodes will get involved into other things like drawing arrows between them. ;-) – marmot Aug 22 '18 at 14:03
  • @Erlkoenig I agree with you that it is fiddly, and added a not-so-fiddly TikZless solution. If you intend to add arrows between these nodes, this can also be done using TikZ overlays. To the best of my knowledge there is no equivalent of \hfill inside TikZ. It might, however, be doable to do that, yet if that's what you're after, please ask a new question. – marmot Aug 22 '18 at 14:11
8

I just discovered TikZ's execute at begin node function. Very handy. It is used on pages 79 and 80 of the TikZ user guide, though I cannot find it discussed there. This essentially does what @marmot proposes but uses a \strut to accomplish it.

\documentclass[tikz]{standalone}
\usetikzlibrary{positioning}
\begin{document} 
\begin{tikzpicture}[every node/.style={draw,execute at begin node=\strut}]
    \node (a1) { A1 };
    \node (a2) [right=of a1] { A2gq };
    \node (a3) [right=of a2] { A3 };
    \node (b1) [below=of a1] { B1 };
    \node (b2) [right=of b1] { B2 };
    \node (b3) [right=of b2] { B3gq };
    \node (b4) [right=of b3] { B4 };
    \node (c1) [below=of b1] { C1 };
    \node (c2) [right=of c1] { C2gq };
    \node (c3) [right=of c2] { C3 };
\end{tikzpicture}

\end{document}

Aligned nodes.

Update

As I am not a fan of excessive markup, that word 'fiddly' got my attention, as did @marmot's non-TikZ solution (smart!). So I offer this:

\documentclass{article}

\usepackage{tikz}   
%% #1 the distance over which nodes are spread;
%% #2 comma-separated list of node contents.
\newcommand{\stretchtowidth}[2]{%
    \begingroup
    \tikzset{every node/.style={draw}}%
    \hbox to #1{%
        \foreach \n in {#2}{\tikz\node{\strut\n};\hfill}\unskip}%
    \endgroup
}

\begin{document} 

\stretchtowidth{1.25in}{A1,A2gq,A3}

\medskip

\stretchtowidth{1.25in}{B1,B2,B3gq,B4}

\medskip

\stretchtowidth{1.25in}{C1,C2gq,C3}

\end{document}

New sample.

  • +1 Consider removing the draws in the nodes, you have already taken care of them by saying every node/.style={draw.... – marmot Aug 22 '18 at 12:43
  • @marmot Quite right! Was on my to-do list when I noticed the addtional OP requirement to distribute the nodes to a given width. I got distracted... Fixed. Thanks. – sgmoye Aug 22 '18 at 12:47
  • I also removed a spurious on grid. And used the positioning library. – sgmoye Aug 22 '18 at 12:56
  • Yes, and I updated such that the outer ends are aligned, a requirement that seems to have been ignored by all three of us. ;-) – marmot Aug 22 '18 at 13:03
5

A solution using the chains library.

\documentclass[tikz]{standalone}
\usetikzlibrary{chains}
\begin{document} 
    \begin{tikzpicture}[
        start chain=A going right,
        start chain=B going right,
        start chain=C going right,
        outer sep=0pt,
        inner sep=.2em,
        node distance=1.6em and .25em,
        every node/.style={draw, anchor=base, text height=0.8em, text depth=0.25ex}]
        \node (a1) [on chain=A] {A1};
        \node [on chain=A] {A2gq};
        \node [on chain=A] {A3};
        \node (b1) [on chain=B, below of=a1] {B1};
        \node [on chain=B] {B2};
        \node [on chain=B] {B3gq};
        \node [on chain=B] {B4};
        \node [on chain=C, below of=b1] {C1};
        \node [on chain=C] {C2gq};
        \node [on chain=C] {C3};
    \end{tikzpicture}
\end{document}

enter image description here

  • Thanks, but how to make sure that the chains have equal length, i.e. expand nodes in chains that are smaller than the longest chain - a little like "justified" (in opposition to ragged-left) text? – Erlkoenig Aug 22 '18 at 11:22
  • Whoops I missed that point… I've got to think about it. – NBur Aug 22 '18 at 11:24
3

When these distributions are more o less regular, you can use a tcbposter (from tcolorbox). Following code tries to mimmic linked protocol stack with a tcbposter. I've supposed a 8 columns x 9 rows distribution where right most column is two column wide. As first 8 rows show a regular distribution it has been easy to place all boxes.

For the 9th row I used width tcolorbox option instead of span to define the correct width after solving following equation for \mylength

5*\mylength+4*\tcbpostercolspacing = 6*\tcbpostercolwidth + 5*\tcbpostercolspacing

After that, placement of boxes are fixed with xshift.

\documentclass[border=2mm]{standalone}

\usepackage[most]{tcolorbox}
\usepackage{lmodern}
\usepackage{lipsum}
%\usepackage{geometry}

\standaloneenv{tcbposter}

%\pagestyle{empty}
\begin{document}

\begin{tcbposter}[%
    poster = {columns=8, rows=9, width=17cm, height=8cm, spacing=1mm},% showframe},
    boxes = {colback=cyan!80!black, 
        boxrule=0pt, arc=2mm,
        colframe=cyan!80!black, 
        halign=center, valign=center,   
        colupper=white,
        fontupper=\sffamily\bfseries, size=small}
]
%1st row
\posterbox{column=1, row=1}{HTTP}
\posterbox{column=2, row=1}{HTTP/2}
\posterbox{column=3, row=1}{MQTT}
\posterbox{column=4, row=1}{CoAP}
\posterbox{column=5, row=1}{FTP}
\posterbox{column=6, row=1}{TFTP}

%2nd row
\posterbox{column=1, row=2}{SMTP}
\posterbox{column=2, row=2}{SNTP}
\posterbox{column=3, row=2}{DNS}
\posterbox{column=4, row=2}{NetBIOS}
\posterbox{column=5, row=2, span=2}{SNMPv1/v2c/v3}

%3rd row
\posterbox{column=1, row=3, span=2}{WebSocket}
\posterbox{column=3, row=3}{mDNS}
\posterbox{column=4, row=3}{DNS-SD}
\posterbox{column=5, row=3}{DHCP}
\posterbox{column=6, row=3}{DHCPv6}

%4th row
\posterbox{column=1, row=4,span=6}{Socket}

%5th row
\posterbox{column=1, row=5, span=2.5}{TCP}
\posterbox{column*=5, row=5, span=2.5}{UDP}
\posterbox{column=6, row=5}{RAW}

%6th row
\posterbox{column=1, row=6, span=3}{IPv4}
\posterbox{column=4, row=6, span=3}{IPv6}

%7th row
\posterbox{column=1, row=7, span=1.5}{ARP}
\posterbox{column*=3, row=7, span=1.5}{Auto-IP}
\posterbox{column=4, row=7, span=1.5}{NDP}
\posterbox{column*=6, row=7, span=1.5}{SLAAC}

%8th row
\posterbox{column=1, row=8, span=1.5}{ICMP}
\posterbox{column*=3, row=8, span=1.5}{IGMPv2}
\posterbox{column=4, row=8, span=1.5}{ICMPv6}
\posterbox{column*=6, row=8, span=1.5}{MLDv1}

%9th row
%5 boxes and 4 separations should use 
%equivalent to 6 original boxes plus 5 
%separations 
\newlength{\mylength}
\pgfmathsetlength{\mylength}{(6*\tcbpostercolwidth+\tcbpostercolspacing)/5}%

%Use `width` instead of `span` to fix box size
\posterbox[width=\mylength]{name=91, column=1, row=9}{Ethernet}
\posterbox[width=\mylength]{name=92, column=1, row=9, xshift=\mylength+\tcbpostercolspacing}{Wi-Fi}
\posterbox[width=\mylength]{name=92, column=1, row=9, xshift=2*\mylength+2*\tcbpostercolspacing}{PPP}
\posterbox[width=\mylength]{name=92, column=1, row=9, xshift=3*\mylength+3*\tcbpostercolspacing}{USB/RNDIS}
\posterbox[width=\mylength]{column*=6, row=9}{G3-PLC}

%Right column
\posterbox[colback=gray, colframe=gray, colupper=black]{column=7, row=1, span=2, rowspan=3}{7 - Application}
\posterbox[colback=gray!80, colframe=gray!80, colupper=black]{column=7, row=4, span=2}{5 - Session}
\posterbox[colback=gray!60, colframe=gray!60, colupper=black]{column=7, row=5, span=2}{4 - Transport}
\posterbox[colback=gray!40, colframe=gray!40, colupper=black]{column=7, row=6, span=2, rowspan=3}{3 - Network}
\posterbox[colback=gray!20, colframe=gray!20, colupper=black]{column=7, row=9, span=2}{2 - Data Link}

\end{tcbposter}

\end{document}

enter image description here

  • Excellent example, solved all my problems aside for one short search for "posterbox rowspan" – Mark K Cowan Nov 14 '18 at 16:07
1

Thank to @Ignasi who pointed out the tcolorbox package, here is another solution with the raster functionality.

\documentclass[12pt, a4paper]{article}
\usepackage{tcolorbox}
\tcbuselibrary{most, raster}

\begin{document}
    \tcbset{size=small, colframe=red!50!black, colback=red!10!white, raster equal height=rows ,halign=center, valign=center}
    \begin{tcbitemize}[raster columns=3]
        \tcbitem A1
        \tcbitem A2gq
        \tcbitem A3
    \end{tcbitemize}
    \begin{tcbitemize}[raster columns=4, colframe=blue!50!black, colback=blue!10!white]
        \tcbitem B1
        \tcbitem B2
        \tcbitem B3gq
        \tcbitem B4
    \end{tcbitemize}
    \begin{tcbitemize}[raster columns=3]
        \tcbitem C1
        \tcbitem C2gq
        \tcbitem C3
    \end{tcbitemize}
\end{document}

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.