10

I am in the processing of writing a block diagram that emulates the following figure using tikz.

sample mrac

The code I generated starts from c(t) and begins a flow process to the rest of the blocks. It is shown below.

    \documentclass{standalone}

    \usepackage{tikz}
    \usetikzlibrary{arrows,positioning,patterns,decorations.pathmorphing,calc}


    \begin{document}


   \tikzstyle{block} = [draw, fill=none, rectangle, 
    minimum height=2em, minimum width=3em]
\tikzstyle{sum} = [draw, fill=none, circle, node distance=2cm]
\tikzstyle{input} = [coordinate]
\tikzstyle{output} = [coordinate]
\tikzstyle{pinstyle} = [pin edge={to-,thin,black}]
\tikzstyle{mux} = [draw, fill=black, rectangle, 
    minimum height=4em, minimum width=0.2em, inner sep = 0cm]
\tikzstyle{square} = [draw, fill=none, rectangle, 
    minimum height=2em, minimum width=2em]

% The block diagram code is probably more verbose than necessary
\begin{tikzpicture}[>=stealth, auto, node distance=2cm, scale=0.5, every node/.style={transform shape}]
    % We start by placing the blocks
    \node [input, name=input] {};
    \node [mux, right=12 em of input] (mux) {};
    \node [square, left =of  mux.260] (int) {$\int$};
    \node [sum, left= of int] (in) {};  
    \node [coordinate, left =of in] (in2) {};
    \node [block, left =22 em of mux.-260] (unc) {Uncertain System};
    \node [coordinate, right =of  mux] (pt1) {};
    \node [coordinate, right =of  pt1] (pt2) {}; 
    \node [coordinate, below =of  pt2] (pt3) {};
    \node [coordinate, left =of pt3] (pt4) {};
    \node [sum, below =of pt3] (sum1) {}; 
    \node [coordinate, below =of sum1] (pt5) {};
    \node [coordinate, below =2 em of pt5] (pt6) {}; 
    \node [square, left =10 em of pt4] (k1) {$K_1$};
    \node [sum, left =10cm of k1] (sum2) {};
    \node [block, left =6 em of sum1] (ref) {Longitudinal Reference System};
    \node [coordinate, left =of pt5] (pt8) {};
    \node [block, left =6 em of pt8] (wgt) {\begin{tabular}{c}Longitudinal Weight\\ Update Law\end{tabular}}; 
    \node [coordinate, above left=2 em of wgt.180] (pt7) {};
    \node [coordinate, left =8 em of pt6] (pt9) {}; 
    \node [block, left =8 em of wgt] (adp) {\begin{tabular}{c}Longitudinal Adaptive \\ Controller \end{tabular}};

    \draw [draw,->] (in) -- (int);
    \draw [->] (int) -- node {} (mux.260); 
    \draw [->] (mux) -- (pt1) -- (pt2) -- (pt3) -- node [near end] {$+$} (sum1); 
    \draw [->] (pt3) -- (pt4) -- (k1);
    \draw [->] (sum1) -- (pt5) -- (pt6) -- (pt9) -- (pt7);
    \draw [->] (ref) -- node [near end] {$-$} (sum1);
    \draw [->] (pt1) |- (wgt); 
    \draw [->] (wgt) -- (adp); 
    \draw [->] (adp) -| node [near end] {$+$} (sum2); 
    \draw [->] (k1) -- node [near end] {$+$}  (sum2);
    \draw [->] (unc.-5) -| node [near end] {$+$} (in); 
    \draw [->] (unc.5) -- (mux.-263);
    \draw [->] (sum2) |- (unc); 
    \draw [->] (in) |- (ref);
    \draw [->] (in2) --node [below] {$c(t)$} node [near end] {$-$}  (in);
\end{tikzpicture}

\end{document}

which generates the following picture...

long mrac

Quite frankly, I do not like the results for this, this leads to my question of two forms:

  1. Is there a better, more efficient way, to generate this block diagram using tikz?

  2. The mux definition as I have it generates a black block, but it is too large. Is there a way to better way to generate a black vertical bar?

I thank you in advance for your help.

  • It's because you are using adaptive control in 2017 :P – percusse Jan 19 '17 at 7:43
16

I usually start by positioning the nodes, not worrying about spacing. Just place them approximately how you want them to be. I will use the following styles:

\tikzset{
    module/.style = {draw, thick, rectangle, align = center},
    mux/.style = {fill=black, minimum width=0.1cm, minimum height = 1.2cm, inner sep = 0cm},
    sum/.style = {draw, thick, circle, inner sep = 0cm, minimum size = 0.5cm},
    tip/.style = {->, >=stealth', thick},
    rtip/.style = {<-, >=stealth', thick}
}

I use \tikzset and /.style since it's more common and the tikzstyle option is not documented in the manual (maybe old style command? I don't know).

Straightforwardly written down, this yields: First Try

\begin{tikzpicture}
    \node[module] (uncertain) { Uncertain System };
    \node[module, right = of uncertain] (int) { $\displaystyle\int$ };
    \node[mux, right = of int] (mux) { };
    \node[sum, below left = of uncertain] (sum1) { };
    \node[module, right = of sum1] (K) { $K_{long}$ };
    \node[module, below right = of K] (long1) { Longitudinal Adaptive\\Control };
    \node[module, right = of long1] (long2) { Longitudinal Weight\\Update Law };
    \node[module, above right = of long1] (long3) { Longitudinal Reference System };
    \node[sum, right = of long3] (sum2) { };
\end{tikzpicture}

No we fine-tune the spacing of the nodes. The integration block is a bit lower in your example, so we will use a border anchor for the placement (note that with the positiong library, the right= key will use the west anchor as placement anchor.) By using a border anchor, the spacing in x-direction does not change (since the west anchor also lies on the border) but we can shift the node down. I used the 150 anchor. The same holds for the mux block, but here we want to shift the node up, so we will use the 260 anchor (i tried out some values). This yields Shifting with border anchors using

\node[module] (uncertain) { Uncertain System };
\node[module, right = of uncertain, anchor = 150] (int) { $\displaystyle\int$ };
\node[mux, right = of int, anchor = 260] (mux) { };

The relative positioning is fine, now we need to tune the spacing. We will do this by using the node distance key, but with two values:

\begin{tikzpicture}[node distance = 2cm and 1.5cm]

With this, we get Third Try

Not bad! What remains is a little shift of the 'Longitudinal Reference System' and the 'Klong' node. For the first, we will just use a different reference anchor. Currently, we are using north east of the reference node, since we are using above right. We will change this by stating the anchor explicitly. Also, we will change the spacing since this node should be closer to the others:

\node[module, above right = 1cm and 1.0cm of long1.north west] (long3) { Longitudinal Reference System };

Again, i try out these values until i like the result. Note that we will have to shift the second summing point, but this is easy:

\node[sum, right = 3cm of long3] (sum2) { };

We will use the same approach for the 'Klong' node:

\node[module, right = 1cm of sum1] (K) { $K_{long}$ };

And here i made a mistake: we are positioning the lower nodes relative to 'Klong', so we can't just push that node around. Doesn't matter, easy to fix:

\node[module, below right = 2cm and 3.5cm of sum1] (long1) { Longitudinal Adaptive\\Control };

We have to manually adapt the spacings, but that's it (and, of course, the new reference node 'sum1').

The final result is: Final Ok, very nice. I tweaked some values, so the final code is this:

\begin{tikzpicture}[node distance = 1.5cm and 1.5cm] % note the change here
    \node[module] (uncertain) { Uncertain System };
    \node[module, right = of uncertain, anchor = 150] (int) { $\displaystyle\int$ };
    \node[mux, right = of int, anchor = 260] (mux) { };
    \node[sum, below left = of uncertain] (sum1) { };
    \node[module, right = 2.5cm of sum1] (K) { $K_{long}$ };
    \node[module, below right = 2cm and 3.5cm of sum1] (long1) { Longitudinal Adaptive\\Control };
    \node[module, right = of long1] (long2) { Longitudinal Weight\\Update Law };
    \node[module, above right = 1cm and 1.0cm of long1.north west] (long3) { Longitudinal Reference System };
    \node[sum, right = 3cm of long3] (sum2) { };
\end{tikzpicture}

The wiring is quite straightforward: the nodes are placed in a way that allows using implicit anchors. Just take the nodes as coordinates and you're ready to go. The labels and the +-signs are just placed as nodes on the paths:

\draw[tip] (K) -- node[pos=0.9, above] {$+$} (sum1);
\draw[tip] (sum1) |- node[pos=0.8, above] {$\delta_e(t)$} (uncertain);
\draw[tip] (long1) -| node[pos=0.95, right] { $+$ } node[pos=0.15, above] { $u_{along}(t)$ } (sum1);    
\draw[tip] (uncertain) -- node[pos=0.3, above] { $\alpha(t)$, $q(t)$ } (int.150);
\draw[rtip] (int.210) -- ++(-2, 0);

These are the first five connections, the others work the same (make sure to use the right anchors for the mux node). With connections

There are three lines that don't start at a node but on another line. The first one (connecting the input of the integration node and the longitudinal ref. system) is drawn by using a shift:

\draw[tip] ([xshift=-3cm] int.210) |- (long3);

Neat! The second line is the line connecting the output of the mux and the 'Update Law' Module. We could use the same approach as for the first line, but we will need some coordinates for this line (we will see this in a minute). We will use the line connecting the mux and the right summing point:

\draw[tip] (mux) -| coordinate[pos=0.35] (c0) node[pos=0.2, above] { $x_{long}(t)$ } (sum2);

Note how i used two nodes, one for the label, one as coordinate (a coordinate is a special node). I'm not very creative with coordinate names, mine are always enumerated (ci)'s.

Ok, now we have the coordinate, so we can draw the second line:

\draw[tip] (c0) |- (long2);

The last line is tricky: we need the starting coordinate, which lies exactly at the height of the Klong node. How do we do this? We could first draw this line and define the corner as coordinate, then draw the line to the 'Update Law' node. Works, but not elegant and we need another coordinate. I can do it without a new coordinate: by using the great syntax of specifying two nodes in a coordinate:

\draw[tip] (c0 |- K.east) -- (K);

So, what's going on here? We draw a line from some point to K. Ok, but where is this some point? It lies at the point where the two imaginary lines through c0 (vertically) and K.east (horizontly) meet. How cool is this?

Currently, we have this: enter image description here

For the line drawn over the lower right node we will use the calc library:

\draw[tip] (sum2.south) |- node[pos=0.2, right] { $e_{long}(t)$ } ($(long2.south east)+(-0.5, -0.5)$) -- ($(long2.north west)+(0.5, 0.5)$);

So we start at the summing point, draw a vertical and a horizontal line to a point near the node, than cross it to the other side. Looks like much, but i think it is quite clear.

One more thing to solve: how do we get the look that the line 'overlap' each other? This one is tricky, and my solution is not perfect. There is a key which allows you to do this very easy: double. It draws the line twice, one thin one, one thicker one, in different colors. This yields the desired effect. From the tikzmanual:

\draw (0, 0) -- (1, 1);
\draw[draw=white, double = red, very thick] (0, 1) -- (1, 0);

Double lines tikzmanual

Ok, this has the desired effect (think of the background as white). Let's use this:

\draw[tip, white, double = black] ([xshift=-3cm] int.210) |- (long3);

The result of this is: Double crossing Well, looks ok, but the arrow is gone! Hm, how do we solve this? Here comes the trick: we will just draw the line twice per hand. Let's introduce a new style:

\tikzset{
    doubletip/.style = {white, line width=2.5pt, line cap = butt}
}

The line cap option tells the line to 'stop' where it should stop. With the values rect or round there will we an extension. Not using this draws some white paint on the line where it starts. We don't want that.

Let's draw both lines:

\draw[doubletip] ([xshift=-3cm] int.210) |- (long3.west);
\draw[tip] ([xshift=-3cm] int.210) |- (long3);
\draw[doubletip] (long3) -- (sum2);
\draw[tip] (long3) -- node[pos=0.9, above] { $-$ } (sum2);

There is still one problem: White paint Although we used the line cap option, we still have some overlapping. This is ugly! We could shorten the line in some way, but easier is just to draw the black line (from left to right) after to white line.

The final image is: Final Image

If you followed my code step-by-step, you will see that you won't get the exact same result. While drawing i'm always tweaking some values here and there since i always encounter some positioning problems etc. So here's the full code:

\documentclass[landscape]{article}

\usepackage[ngerman]{babel}

\usepackage{tikz}
\usetikzlibrary{arrows, positioning, calc}

\tikzset{
    module/.style = {draw, thick, rectangle, align = center},
    mux/.style = {fill=black, minimum width=0.1cm, minimum height = 1.2cm, inner sep = 0cm},
    sum/.style = {draw, thick, circle, inner sep = 0cm, minimum size = 0.5cm},
    tip/.style = {->, >=stealth', thick},
    doubletip/.style = {white, line width=3pt, line cap=butt},
    rtip/.style = {<-, >=stealth', thick} % reverse tip
}

\begin{document}
\begin{tikzpicture}[node distance = 1.5cm and 1.5cm]
    % modules
    \node[module] (uncertain) { Uncertain System };
    \node[module, right = 4cm of uncertain, anchor = 150] (int) { $\displaystyle\int$ };
    \node[mux, right = of int, anchor = 260] (mux) { };
    \node[sum, below left = of uncertain] (sum1) { };
    \node[module, right = 2.5cm of sum1] (K) { $K_{long}$ };
    \node[module, below right = 2cm and 3.5cm of sum1] (long1) { Longitudinal Adaptive\\Control };
    \node[module, right = of long1] (long2) { Longitudinal Weight\\Update Law };
    \node[module, above right = 1cm and 2.5cm of long1.north west] (long3) { Longitudinal Reference System };
    \node[sum, right = 4cm of long3] (sum2) { };
    % connections
    \draw[tip] (K) -- node[pos=0.9, above] {$+$} node[pos=0.4, above] { $u_{nlong}(t)$ } (sum1);
    \draw[tip] (sum1) |- node[pos=0.8, above] {$\delta_e(t)$} (uncertain);
    \draw[tip] (long1) -| node[pos=0.95, right] { $+$ } node[pos=0.15, above] { $u_{along}(t)$ } (sum1);
    \draw[tip] (uncertain) -- node[pos=0.3, above] { $\alpha(t)$, $q(t)$ } (int.150);
    \draw[tip] (int) -- (mux.260);
    \draw[tip] ($(uncertain.east)!0.8!(int.150)$) |- (mux.100);
    \draw[tip] (mux) -| coordinate[pos=0.35] (c0) node[pos=0.2, above] { $x_{long}(t)$ } (sum2);
    \draw[tip] (long2) -- node[above] { $W_{long}(t)$ } (long1);
    \draw[tip] (c0) |- (long2);
    \draw[tip] (c0 |- K.east) -- (K);
    \draw[doubletip] ([xshift=-3cm] int.210) |- (long3.west);
    \draw[tip] ([xshift=-3cm] int.210) |- (long3);
    \draw[tip] (sum2.south) |- node[pos=0.2, right] { $e_{long}(t)$ } ($(long2.south east)+(-0.5, -0.5)$) -- ($(long2.north west)+(0.5, 0.5)$);
    \draw[doubletip] (long3) -- (sum2);
    \draw[tip] (long3) -- node[pos=0.9, above] { $-$ } (sum2);
    \draw[rtip] (int.210) -- node[pos=0.85, below left] { $c_{long}(t)$ } ++(-3.5, 0);
\end{tikzpicture}

\end{document}
  • 1
    This is a much more elegant solution that the one I've been doing. Thank you for your input. – trcomet Jan 18 '17 at 23:49
  • \tikzstyle is from old versions of TikZ, it's deprecated so that's why it's not present in the current manual. It works still only for compatibility reasons but the handler /.style should be used. Furthermore, Till Tantau recommends the use of /.style whitin the tikzpicture, to keep it local and make the whole Picture copy-pastable. Nice drawing btw, might I suggest compiling everything to make and MWE with \documentclass and \end{document}? :) – Guilherme Zanotelli Jan 19 '17 at 7:08
  • Thank you for the explanation! I just added a compilable document :) – pschulz Jan 19 '17 at 7:34
  • Considering using /.style local within tikzpictures: i agree, if you only have one picture. Since this is nearly never the case for me, i always have big chunks of \tikzset's in my preambel so that all pictures can be changed from one place. – pschulz Jan 19 '17 at 7:35
  • You can define various /.style within a unique \tikzset you have only to remember to separate them with commas, see here for a funny example. – CarLaTeX Jan 19 '17 at 7:46

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.