0

I am currently creating technical documentation that depicts through a tikzpicture the data flow across something called the I2C bus (example-like graphic). I initially started doing this by hand with the following result: enter image description here

As it is shown, my depiction uses a line cross pattern drawn around each text element. I eventually stopped doing this by hand (i.e. this graphic is incomplete as shown) in hopes that I could create a macro function to do this for an arbitary list of text elements with consistent spacing.

I came up with the following (broken) example:

\documentclass{article}
\usepackage[utf8x]{inputenc}
\usepackage{tikz}
\usepackage{graphicx,pgf}
\usepackage{listofitems}

\begin{document}

% @brief Creates a test graphic. % @param[in] #1 The x-coordinate of the graphic. % @param[in] #2 The y-coordinate of the graphic. % @param[in] #3 A comma separated list of strings. \newcommand{\CreateTestGraphic}[3] {% \begin{tikzpicture}

    \readlist*\mylist{#3}                   % Read the comma separated input array.

    \newcommand{\CrossHorDistance}{15pt}    % The horizontal distance of the cross.
    \newcommand{\SeparationDistance}{15pt}  % The horizontal distance to separate each text element by.

    \edef\NextTextCenterX{#1}               % Keeps track of the text's center x-coordinate.
    \edef\NextTextCenterY{#2}               % Keeps track of the text's center y-coordinate.

    \foreachitem \x \in \mylist
    {%
        \settowidth{\strwidth}{\x}      % The width of the string at the current font and size in
                                        % point (pt) units.
        \settoheight{\strheight}{\x}    % The height of the string at the current font and size in
                                        % point (pt) units.

        \coordinate (NextPosition) at (\NextTextCenterX, \NextTextCenterY); % The center coordinate position
                                                                            % to place the next text element at.

        % For testing only: Place a dot to show where the center coordinate is on screen.
        %\node at (NextPosition) [circle,
        %                         fill,
        %                         inner sep=1.5pt]{};

        % Paint the text to the page.
        \node[] (NextNode) at (NextPosition) {\x};

        \draw[color=black]  ([yshift=5pt] NextNode.north west) --
                            ([yshift=5pt] NextNode.north east) --
                            ([yshift=-5pt, xshift=\CrossHorDistance] NextNode.south east);

        \draw[color=black]  ([yshift=-5pt] NextNode.south west) -- 
                            ([yshift=-5pt] NextNode.south east) --
                            ([yshift=5pt, xshift=\CrossHorDistance] NextNode.north east);

        % Update the variables that track the x and y coordinates of where to place the next text.
        %\pgfmathparse{\NextTextCenterX + (\strwidth - 20pt)}
        \pgfmathparse{\NextTextCenterX + 5pt} % This doesn't work when using \strwidth...
        \edef\NextTextCenterX{\pgfmathresult}
    }

\end{tikzpicture}

}

\CreateTestGraphic{0pt}{0pt}{apple, blueberry, banana}

\end{document}

(This was compiled on Overleaf.com using the XeLaTeX compiler.)

This code produces the following (broken and incomplete) results: Broken function output Although, errors appear in the editor despite the graphical output: Compiler Error/Warning

I've spent a lot of time researching to try and bridge the gap in my knowledge with the TeX engine. Here are the major questions I have:

  • How can I create a numerical variable that I can update by an arbitary value (like normal programming languages)? I've tried using \edef, \newcommand, etc. but nothing seems to work the way I expect.
    Example: double var = 15pt; var += 73pt
    Context: Nodes are drawn in tikz at a center coordinate. If I want to position each text element equidistant away to allow the crossing lines to flow consistently, I need to update the next node's center coordinate.

  • How the heck do I get the width and height in units of points (pt) for text?
    Context: As noted in the above question, because I need to continuously keep track of the next node's center coordinate, I need to understand the dimensions of the incoming text. I have tried both width("\x") and \settowidth{\wvar}{\x}, but I haven't had much luck using these in arthemtic when I try and do width("\x") * 0.5pt, etc. I also need to get the maximum height of the text, so I can draw the line at the correct height for all the graphics.

In the sample code, I was unsuccessfully using the \strwidth variable in the following express: \pgfmathparse{\NextTextCenterX + (\strwidth * 0.5pt)}. I am at a loss here on how I can actually maintain updatable variables and how I can get the numerical width and height of text that I can then use in a variable to manipulate.

Is anyone able to help put me back on the right track and answer my above questions?


Update After Accepting Answer (5/18/2022):

Thanks to @Tom's answer below, the desired graphic output was achieved! I did make a slight modification though. As shown in the initial picture to my question, I was looking to add an arrow, which would not be enclosed, to show the direction of movement in the graph. So, I modified @Tom's answer slightly and the final, example code was created:

\documentclass{article}
\usepackage{tikz}
\usepackage{graphicx,pgf}
\usepackage{listofitems}

\usetikzlibrary{arrows,calc}

% @brief Creates a test graphic. % @param[in] #1 The x-coordinate and y-coordinate of the graphic. % @param[in] #2 A comma separated list of strings. \newcommand{\CreateTestGraphic}[2] {% \newcommand{\CrossHorDistance}{15pt} % The horizontal distance for each cross.

\begin{tikzpicture}

    \begin{scope}[shift={(#1)}]

        %
        %   Create a blank node and draw the initial starting cross and direction arrow.
        %
        \coordinate (NextNode) at (0,0);

        \node[inner sep=0pt,
              minimum size=1cm,
              align=center,
              right=\CrossHorDistance] (NextNode) at (NextNode.east) {};

        \draw [-stealth] ([xshift=-15pt-\CrossHorDistance] NextNode.west) --
                         ([xshift=0pt-\CrossHorDistance] NextNode.west);

        \draw[color=black]  ([yshift=5pt] NextNode.north west) --
                            ([yshift=-5pt, xshift=-\CrossHorDistance] NextNode.south west) --
                            ($([yshift=-5pt, xshift=-\CrossHorDistance] NextNode.south west) + (-5pt, 0pt)$);

        \draw[color=black]  ([yshift=-5pt] NextNode.south west) --
                            ([yshift=5pt, xshift=-\CrossHorDistance] NextNode.north west) --
                            ($([yshift=5pt, xshift=-\CrossHorDistance] NextNode.north west) + (-5pt, 0pt)$);

        % Reset the 'NextNode' in preparation for painting all of the nodes.
        \coordinate (NextNode) at (0,0);

        \foreach \content in {#2}
        {%
            \node[text depth=.3\baselineskip,
                  text height=.7\baselineskip,
                  inner sep=0pt,
                  minimum size=1cm,
                  align=center,
                  right=\CrossHorDistance] (NextNode) at (NextNode.east) {\content};

            \draw[color=black]  ([yshift=5pt] NextNode.north west) --
                                ([yshift=5pt] NextNode.north east) --
                                ([yshift=-5pt, xshift=\CrossHorDistance] NextNode.south east);

            \draw[color=black]  ([yshift=-5pt] NextNode.south west) --
                                ([yshift=-5pt] NextNode.south east) --
                                ([yshift=5pt, xshift=\CrossHorDistance] NextNode.north east);
        }

        \draw[color=black]  ([yshift=-5pt, xshift=\CrossHorDistance] NextNode.south east) --
                            ($([yshift=-5pt, xshift=\CrossHorDistance] NextNode.south east) + (5pt, 0pt)$);

        \draw[color=black]  ([yshift=5pt, xshift=\CrossHorDistance] NextNode.north east) --
                            ($([yshift=5pt, xshift=\CrossHorDistance] NextNode.north east) + (5pt, 0pt)$);

    \end{scope}

\end{tikzpicture}

}

\begin{document}

\CreateTestGraphic{0pt, 0pt}{apple, blueberry, banana}

\end{document}

(This was compiled on Overleaf.com using the XeLaTeX compiler.)

Final Output: final final final output


Update #2 After Accepting Answer (5/18/2022):

I have spent some time exploring how I might be able to color each individual cell. In order to do this, it required me to change how things were drawn to the screen. To anyone who might be interested, here is a working solution example:

\documentclass{article}
\usepackage{tikz}
\usepackage{graphicx,pgf}
\usepackage{listofitems}
\usepackage{ifthen}

\usepackage{pgfplots} \pgfplotsset{compat=1.11} \usepgfplotslibrary{fillbetween} \usetikzlibrary{intersections}

\pgfdeclarelayer{bg} \pgfsetlayers{bg,main}

\usetikzlibrary{arrows,calc}

% @brief Creates a test graphic. % @param[in] #1 The x-coordinate and y-coordinate of the graphic. % @param[in] #2 A comma separated list of colors. % @param[in] #3 A comma separated list of strings. \newcommand{\CreateTestGraphic}[3] {% \newcommand{\CrossHorDistance}{15pt} % The horizontal distance for each cross.

\begin{tikzpicture}

    \begin{scope}[shift={(#1)}]

        %
        %   Create a blank node and draw the initial starting cross and direction arrow.
        %
        \coordinate (NextNode) at (0,0);

        \node[inner sep=0pt,
              minimum size=1cm,
              align=center,
              right=\CrossHorDistance] (NextNode) at (NextNode.east) {};

        \draw [-stealth] ([xshift=-15pt-\CrossHorDistance] NextNode.west) --
                         ([xshift=0pt-\CrossHorDistance] NextNode.west);

        \draw[color=black]  ([yshift=0pt, xshift=-\CrossHorDistance/2] NextNode.west) --
                            ([yshift=-5pt, xshift=-\CrossHorDistance] NextNode.south west) --
                            ($([yshift=-5pt, xshift=-\CrossHorDistance] NextNode.south west) + (-5pt, 0pt)$);

        \draw[color=black]  ([yshift=0pt, xshift=-\CrossHorDistance/2] NextNode.west) --
                            ([yshift=5pt, xshift=-\CrossHorDistance] NextNode.north west) --
                            ($([yshift=5pt, xshift=-\CrossHorDistance] NextNode.north west) + (-5pt, 0pt)$);

        % Reset the 'NextNode' in preparation for painting all of the nodes.
        \coordinate (NextNode) at (0,0);

        \readlist*\colors{#2}

        \foreach \content [count=\i] in {#3}
        {%
            \ifthenelse{\equal{\colors[\i]}{none}}
            {%
                \def\NextColor{none}
            }
            {%
                \def\NextColor{\colors[\i]!100}
            }

            \node[text depth=.1\baselineskip,
                  text height=.7\baselineskip,
                  inner sep=0pt,
                  minimum size=1cm,
                  align=center,
                  right=\CrossHorDistance] (NextNode) at (NextNode.east) {\content};

            \begin{pgfonlayer}{bg}             
                \draw  [color= black,
                        fill = \NextColor,
                        fill opacity=0.5]   ([yshift=5pt] NextNode.north west) --
                                            ([yshift=5pt] NextNode.north east) --
                                            ([yshift=0pt, xshift=\CrossHorDistance/2] NextNode.east) --
                                            ([yshift=-5pt, xshift=0pt] NextNode.south east) --
                                            ([yshift=-5pt] NextNode.south west) --
                                            ([yshift=0pt, xshift=-\CrossHorDistance/2] NextNode.west) --
                                            ([yshift=5pt, xshift=0pt] NextNode.north west);
            \end{pgfonlayer}
        }

        \draw[color=black]  ([yshift=0pt, xshift=\CrossHorDistance/2] NextNode.east) --
                            ([yshift=-5pt, xshift=\CrossHorDistance] NextNode.south east) --
                            ($([yshift=-5pt, xshift=\CrossHorDistance] NextNode.south east) + (5pt, 0pt)$);

        \draw[color=black]  ([yshift=0pt, xshift=\CrossHorDistance/2] NextNode.east) --
                            ([yshift=5pt, xshift=\CrossHorDistance] NextNode.north east) --
                            ($([yshift=5pt, xshift=\CrossHorDistance] NextNode.north east) + (5pt, 0pt)$);

        \node [] at ([xshift=\CrossHorDistance + 2.5pt] NextNode.east) {\ldots};

    \end{scope}

\end{tikzpicture}

}

\begin{document}

\CreateTestGraphic{0pt, 0pt}{orange, none, red}{apple, blueberry, banana}

\end{document}

(This was compiled on Overleaf.com using the XeLaTeX compiler.)

Final Output: new final output

  • 3
    Please provide a Minimal Working Example (MWE). As it is, your code isn't compilable and we can't know which packages and PGF / TikZ libraries you're loading. – Miyase May 17 '22 at 19:39
  • @Miyase This was a miss on my part (I usually provide a working example) - thanks for requesting this. I just updated my question above with a complete example and detailed the compiler used. – Code Doggo May 17 '22 at 21:23
  • 1
    Your scheme reminds me tikz-timing package. But I don't know if it can serve you. – Ignasi May 18 '22 at 09:26
  • @Ignasi I didn't know this package existed! Thanks for pointing it out to me - I will take a look into it. – Code Doggo May 18 '22 at 18:57
  • 1
    @Code Maybe put the \newcommand stuffs in the preamble will be better? Also the \usepackage[utf8x]{inputenc} was not needed anymore. – Tom May 18 '22 at 19:32
  • @Tom Yeah, you're right about moving the \newcommand out into the preamble. I do have a question related to your implementation. I noticed you set the width of each cell to a set value (via argument #2) (e.g. 2cm). Is there anyway to base each cell's width directly on the length of text for each element? That would mean some cells would be wider than others depending on the length of text per cell. – Code Doggo May 18 '22 at 19:47
  • 1
    @Code You could do that. I will update my answer. – Tom May 18 '22 at 19:50
  • @Tom Thanks for the updated answer below! I really appreciate the evolution here as I've learned a lot from it. I just updated my answer to reflect your last update below! – Code Doggo May 18 '22 at 20:26
  • 1
    @Code This is perfect. Those setting for vertical align are really good. I didn't think about it before. – Tom May 18 '22 at 20:37

1 Answers1

3

You could use this predefined shape in tikz shapes library. And you can define a command using \foreach in a tikzpicture scope as follow. The first argument is the initial location, the second argument will be the list of content you want to type:

\documentclass[border=0.618cm]{standalone}
\usepackage{listofitems}
\usepackage{tikz}
\usetikzlibrary{shapes}
\tikzset{
    mynd/.style={
        signal,draw,
        signal to=west and east,
        minimum size=1cm, 
        align=center,
        signal pointer angle=110,
        right=-\pgflinewidth
    },
}
\newcommand{\CreateGraphic}[2]{
\begin{scope}[shift={(#1)}]
\coordinate (a) (0,0);
\foreach \content in {#2}
\node (a) [mynd,text width=2cm] at (a.east) {\content};
\end{scope}
}

\begin{document} \begin{tikzpicture} \CreateGraphic{0,0}{$\longrightarrow$,(S)tart,7-bit Target (\textbf{Addr})ess,Write Bit,some thing,something longer} \CreateGraphic{0,-1.5}{$\longrightarrow$,(S)tart,7-bit Target (\textbf{Addr})ess,Write Bit} \end{tikzpicture} \end{document}

enter image description here

Using your original approach:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}
\newcommand{\CrossHorDistance}{15pt}
\newcommand{\CreateTestGraphic}[3]{%
\begin{scope}[shift={(#1)}]
\coordinate (NextNode) at (0,0);
\foreach \content in {#3}{
\node[inner sep=0pt,minimum size=1cm,text width=#2,align=center] (NextNode) at ($(NextNode.east)+(#2/2+\CrossHorDistance,0)$) {\content};
\draw[color=black]  ([yshift=5pt] NextNode.north west) -- ([yshift=5pt] NextNode.north east) -- ([yshift=-5pt, xshift=\CrossHorDistance] NextNode.south east);
\draw[color=black]  ([yshift=-5pt] NextNode.south west) -- ([yshift=-5pt] NextNode.south east) -- ([yshift=5pt, xshift=\CrossHorDistance] NextNode.north east);
}
\end{scope}
}

\begin{document} \begin{tikzpicture} \CreateTestGraphic{0,0}{2cm}{$\longrightarrow$,apple,blueberry longer,banana}% #1(initial position) #2(text width) #3(list of content) \CreateTestGraphic{0,-2}{2cm}{$\longrightarrow$,apple,blueberry longer,banana,Write Bit} \end{tikzpicture} \end{document}

enter image description here

Without define text width

\documentclass{article}
\usepackage{tikz}
\newcommand{\CrossHorDistance}{15pt}
\newcommand{\CreateTestGraphic}[2]{%
\begin{scope}[shift={(#1)}]
\coordinate (NextNode) at (0,0);
\foreach \content in {#2}{
\node[inner sep=0pt,minimum size=1cm,align=center,right=\CrossHorDistance] (NextNode) at (NextNode.east) {\content};
\draw[color=black]  ([yshift=5pt] NextNode.north west) -- ([yshift=5pt] NextNode.north east) -- ([yshift=-5pt, xshift=\CrossHorDistance] NextNode.south east);
\draw[color=black]  ([yshift=-5pt] NextNode.south west) -- ([yshift=-5pt] NextNode.south east) -- ([yshift=5pt, xshift=\CrossHorDistance] NextNode.north east);
}
\end{scope}
}

\begin{document} \begin{tikzpicture} \CreateTestGraphic{0,0}{$\longrightarrow$,apple,blueberry longer,banana}% #1(initial position) #2(text width) #3(list of content) \CreateTestGraphic{0,-2}{blueberry longer,banana,Write Bit} \end{tikzpicture} \end{document}

enter image description here

Tom
  • 7,318
  • 4
  • 21
  • Whoa, this has reminded me to "work smarter, not harder!" Is there anyway you could put this generation into a for loop? I see each node is dependent on the node that came before it, so is there a way to loop through a list and place each node next to each other instead of defining them one-by-one? – Code Doggo May 17 '22 at 21:30
  • I am not quite understand the meaning about loop through a list. – Tom May 17 '22 at 21:34
  • 1
    @Code I update my answer. Is this what you wanted? – Tom May 17 '22 at 21:57
  • This is exactly what I have been looking for! I learned something new looking at your code. I see you continue to "update" the node NextNode / a in the loop by using the cardinal direction (e.g. east, west) of the previously created node. It first starts off as just a "coordinate" and then its type becomes a node of the same name. I didn't know this was possible as this wasn't very clear in general, so I learned something new here! :) Thank you for educating me! I've accepted your answer and also appended my own answer with a slightly modified version of your code. Thank you again! :) – Code Doggo May 18 '22 at 18:55
  • @Code You are welcome! – Tom May 18 '22 at 19:01
  • From your last update, I see the node text isn't aligned height wise depending on what text is inputted. For example, "blueberry" and "banana" aren't aligned in the vertical direction. I found that adding text depth=.3\baselineskip,text height=.7\baselineskip to the node style (ref) fixed this issue. I updated my question above to reflect this change too. But, I just wanted to call it out just in case you had input on this! – Code Doggo May 18 '22 at 20:29