4

I would like to be able to draw inside a tikzdiagram a line that represents a feedback loop like the following

feedback loop

with a command like (but not necessarily exactly like)

\draw[feedback](0,0)to(-1,-1);

I would like that the shape of the loop adjusted to the positions of the endpoints. For example, I would like the straight part to be always a bit below the lowest of the two endpoints (say 1 cm below the lowest of the endpoints), and the loop to be a composition of two semicircles and a straight line.

I would like this command to work with nodes as well as with coordinates as inputs.

The code I used to generate this picture is:

\usepackage{fontawesome} % I think this is necessary for markings
\usetikzlibrary{decorations.markings} % markings
\tikzset{->-/.style={decoration={markings, mark=at position 0.5 with {\arrow{>>}}}, postaction={decorate}}}

\begin{document} \begin{tikzpicture} \drawout=0,in=90to(1,-1)[out=-90,in=0]to(0,-2); \drawout=180,in=0,->-to(-1,-2); \drawout=180,in=-90to(-1.5,-1.5)[out=90,in=180]to(-1,-1); \end{tikzpicture} \end{document}

How can I define a line style with tikzset or define a \newcommand for this?

Elena
  • 97
  • 5
  • Can you describe the curve a bit better? Looks like semicircle, straight part, semicircle. How is offset of the straight part evaluated? Does this also need to work with nodes or only coordinates? – Qrrbrbirlbel Nov 25 '22 at 16:16

2 Answers2

5
\usepackage{fontawesome} % I think this is necessary for markings

No, it isn't. You need to load the package tikz for TikZ and (as you did) one of the decorations libraries.

But first things first. You can write all that path down as one path (if we're ignoring the decoration for now):

\begin{tikzpicture}
\draw (0,0) to[out=  0, in= 90] ( 1,-1)
            to[out=-90, in=  0] ( 0,-2)
            --                  (-1,-2)
            to[out=180, in=-90] (-1.5, -1.5)
            to[out= 90, in=180] (-1,-1);
\path[->-] (0,-2) -- (-1,-2);
\end{tikzpicture}

We could also write each semicircle with only one to but with looseness = 2 added or we could use the arc operation.

Which we will because it makes it easier down the road:

\begin{tikzpicture}
\draw[delta angle=-180]
      (0,0) arc[start angle= 90, radius=1] coordinate (endofarc)
            --                  (-1,-2)    coordinate (endofstraight)
            arc[start angle=-90, radius=.5];
\path[->-] (endofarc) -- (endofstraight);
\end{tikzpicture}

And now we have to parametrize that.

Let's assume you, the user, want to have the straight part a fixed distance #1 below (or above) the target point.

The radius of the second semicircle is then (#1)/2 and the radius of the first semicircle is the difference between both coordinates in y direction plus #1and half of that everything.

Then we just need to figure out the needed angles (yes, to would make it easier because we just specify the directions but we need to calculate more intermediate points for that, if I'm seeing that correctly). For this I set up some simple tests, the scalar function is necessary because it would otherwise return 0pt (for false) and 1pt (for true) but the following ifthenelse (via ?/:) doesn't like that anymore.

Now, while TikZ supports decorating just a part of a path the used decoration destroys the segment and I can't see a way to postaction this. But we can use the edge inside our to path because this is for all intents and purposes a seperate path. The line to specifies the needed path (which is usually the default but since we're inside a to path it would result otherwise in a endless loop).

Even though, the decoration destroys the path I'm playing it safe with path only which disables all actions on a path.


We could extend this so that a paramater #1 without a unit specifies a factor of the vertical distance between the coordinate but that needs a bit more work because we need to evaluate #1 first before we can do this calculations.

As suggested by hpekristiansen I'll add \tikztonodes after for the -- part so that you can place nodes along that straight part.


Instead of the decorations.markings we can use a pic to place an arrow tip along the path. For this, only \pgfarrowdraw is necessary which – apparently, it's not documented – draws the specified arrowtip at the current position.

Code (with decorations.markings)

\documentclass[tikz, border=2.5pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{
  decorations.markings, % → markings decoration
  calc,                 % → let … in
}
\tikzset{
  ->-/.style={
    decoration={
      markings,
      mark=at position 0.5 with {\arrow{>>}}
    },
    postaction=decorate
  },
  feedback/.default=1cm,
  feedback/.style={% #1 a distance
    to path={
      let \p{start}       = (\tikztostart),
          \p{target}      = (\tikztotarget),
          \n{radiusA}     = {(#1)/2+abs(\y{start}-\y{target})/2},
          \n{firstArcUp}  = {scalar(\y{start}>\y{target})},
          \n{arcClockwise}= {scalar(\x{start}>\x{target}!=\y{start}>\y{target})}
      in
      arc[start angle=(\n{firstArcUp}?90:-90),
          delta angle=(\n{arcClockwise}?-180:180),
          radius=\n{radiusA}]
      coordinate (end of feedback arc)
      -- (end of feedback arc-|\tikztotarget)
      \tikztonodes
      coordinate (end of feedback straight)
      arc[start angle=(\n{firstArcUp}?-90:90),
          delta angle=(\n{arcClockwise}?-180:180)*((#1)<0?-1:1),
          radius={(#1)/2}]
      (end of feedback arc) edge[line to, path only, ->-]
      (end of feedback straight)
    }
  }
}
\begin{document}
\begin{tikzpicture}
\draw[help lines] (-2.75, -2.5) grid (1.25, .75);
\draw ( 0, 0) to[feedback      ] node[below=2pt, scale=.4] {Whee!} (-1  , -1  );
\draw (-1,-1) to[feedback=-.5cm]                                   (-2.5,   .5);
\end{tikzpicture}
\end{document}

Code (without decoration)

\documentclass[tikz, border=2.5pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc} % → let … in
\tikzset{
  feedback/.default=1cm,
  feedback/.style={% #1 a distance
    to path={
      let \p{start}       = (\tikztostart),
          \p{target}      = (\tikztotarget),
          \n{radiusA}     = {(#1)/2+abs(\y{start}-\y{target})/2},
          \n{firstArcUp}  = {scalar(\y{start}>\y{target})},
          \n{arcClockwise}= {scalar(\x{start}>\x{target}!=\y{start}>\y{target})}
      in
      arc[start angle=(\n{firstArcUp}?90:-90),
          delta angle=(\n{arcClockwise}?-180:180),
          radius=\n{radiusA}]
      coordinate (end of feedback arc)
      -- (end of feedback arc-|\tikztotarget)
      pic[midway, sloped, allow upside down] {code=\pgfarrowdraw{>>}}
      \tikztonodes
      coordinate (end of feedback straight)
      arc[start angle=(\n{firstArcUp}?-90:90),
          delta angle=(\n{arcClockwise}?-180:180)*((#1)<0?-1:1),
          radius={(#1)/2}]}}}
\begin{document}
\begin{tikzpicture}
\draw[help lines] (-2.75, -2.5) grid (1.25, .75);
\draw ( 0, 0) to[feedback      ] node[below=2pt, scale=.4] {Whee!} (-1  , -1  );
\draw (-1,-1) to[feedback=-.5cm]                                   (-2.5,   .5);
\end{tikzpicture}
\end{document}

Output

enter image description here

Qrrbrbirlbel
  • 119,821
  • +1. Could decorations.markings be committed somehow(Just because I hate the need to use it)? -e.g. by adding execute at end to. OP has not indicated the need, but I think it would be nice to add \tikztonodes to the straight segment. – hpekristiansen Nov 25 '22 at 17:46
  • 1
    @hpekristiansen What do mean by commited? Remove it? Sure, as I said, we could replace it with a pic, with just an arrow as a marking it is as easy as pic[midway, sloped, allow upside down] {code=\pgfarrowdraw{>>}} (→ I had to look it up in the source what \arrow actually does.). And yes, I forgot about \tikztonodes – Qrrbrbirlbel Nov 25 '22 at 18:20
  • -typo followed by auto correct -I meant "omitted" – hpekristiansen Nov 25 '22 at 18:21
2

I have another (simpler?) code to generate Elena's picture:

\documentclass[12pt,a4paper]{article}
\usepackage{tikz}
\begin{document}
    \begin{tikzpicture}
        \draw (3,2) arc (90:-90:2); 
        \draw[->>-] (3.1,-2)--(2,-2);
        \draw (2,-2)--(1,-2) arc (270:90:1);    
    \end{tikzpicture}
\end{document}

Output:

enter image description here