27

Drawing a short directional arrow, lying parallel to a straight line A-B, is pretty trivial to accomplish using Tikz;

\documentclass{article}
\usepackage{tikz} \usetikzlibrary{calc} \tikzset{>=latex}
\begin{document}

    \begin{tikzpicture}
        \draw (0,0) node[circle, inner sep=0.8pt, fill=black, label={below:{$A$}}] (A) {};  
        \draw (1,0) node[circle, inner sep=0.8pt, fill=black, label={below:{$B$}}] (B) {};  
            \draw (A) to (B);   
        \draw ($(A)!0.15!(B) + (0,0.02)$) node (a) {}; 
        \draw ($(A)!0.35!(B) + (0,0.02)$) node (b) {};  
            \draw[->>] (a) to (b);
    \end{tikzpicture}

\end{document} 

(Correct) Straight line, with parallel straight directional arrow

However, drawing a short curved directional arrow, lying parallel to a curved line C-D, seems to be far more complicated, since the directional arrow's curvature and end points need to be shifted relative to the shape of the curved line;

\documentclass{article}
\usepackage{tikz} \usetikzlibrary{calc} \tikzset{>=latex}
\begin{document}

    \begin{tikzpicture}
        \draw (0,0) node[circle, inner sep=0.8pt, fill=black, label={below:{$C$}}] (C) {};  
        \draw (1,0) node[circle, inner sep=0.8pt, fill=black, label={below:{$D$}}] (D) {};  
            \draw (C) to [bend left=45] (D);
        \draw ($(C)!0.15!(D) + (0,0.02)$) node (c) {}; 
        \draw ($(C)!0.35!(D) + (0,0.02)$) node (d) {};          
            \draw[->>] (c) to [bend left=45] (d);
    \end{tikzpicture}

\end{document} 

(Incorrect) Curved line, with parallel curved directional arrow

I realise that the problem lies with the fact that the short directional arrow's endpoints are defined differently to those of the curved line C-D, and thus, using the [bend left=45] angle option within both curved arrow/line definitions plots the incorrect tikzpicture above.

The answered question 2 arrows at same distance successfully tackles the problem for lines/directional arrows of equal length, but I ran into difficulties when trying to modify its answer for plotting short arrows lying alongside long lines; my main problem was defining and positioning two "extra" thick white lines with which to cover/shorten the directional arrow. Has anyone managed to modify the above answer to answer my question?

I also guessed that [bend left=45] curves were mathematically defined in Tikz using a parabola equation, which then allowed me to produce the desired curved directional arrow/curved line E-F plot below;

\documentclass{article}
\usepackage{tikz} \usetikzlibrary{calc} \tikzset{>=latex}
\begin{document}

    \begin{tikzpicture}
        \draw (0,0) node[circle, inner sep=0.8pt, fill=black, label={below:{$E$}}] (E) {};  
        \draw (1,0) node[circle, inner sep=0.8pt, fill=black, label={below:{$F$}}] (F) {};      
            \draw[domain=0:1, samples=100, black] plot ({\x},{\x*(1-\x)*tan(45)});
            \draw[domain=0.15:0.35, samples=100, black, ->>] plot ({\x},{\x*(1-\x)*tan(45) + 0.02});
    \end{tikzpicture} 

\end{document} 

(Partially Correct) Non-"Bend Left" curved line, with parallel curved directional arrow

However, after further investigation, it seems the [bend left=45] curve shown in red below seems to suggests my guess wasn't completely correct, and [bend left=45] is not given by a parabola equation. Does anyone know what the underlying bend curve equation is? Is it possible to redefine the equation used to plot these curved lines? Or am I missing an easy way of plotting parallel curved lines?

Parabola defined curve (black) vs. [Bend left=45] defined curve

  • 3
    While code snippets are useful in explanations, it is always best to compose a fully compilable MWE that illustrates the problem including the \documentclass and the appropriate packages so that those trying to help don't have to recreate it. This is especially important for tikz as there are numerous libraries. – Peter Grill Jun 10 '14 at 20:28
  • 1
    Sorry; have just updated my question to include all the correct document preambles. Thanks! – Dominic Kerr Jun 10 '14 at 20:40
  • 1
    Neither piece of code produces anything which looks remotely like the images you posted when I compile them! – cfr Jun 10 '14 at 21:31
  • 2
    @cfr: I get something similar by adding scale=10 to the tikzpicture – Herr K. Jun 10 '14 at 23:14
  • A line parallel to a bezier curve is not necessarily again a bezier curve so it is not possible to draw a mathematically accurate parallel line in PDF format. – Loop Space Jun 11 '14 at 8:41
25

The curve drawn by a to[bend left] is probably a bezier curve, and thus is not an arc nor a parabola. Unfortunately this syntas hides the control points used.

Even if the control points were known, they won't be useful for your problem, since basically you want to get another bezier curve parallel to a "segment" or portion of another given bezier curve. I have no idea of the equations that should be solved to find those control points.

But not all hope is lost. I had a crazy idea. Once a bezier path is computed by tikz, any of its intermediate points are available via [pos] key, so it is possible to find a number of those points, between the desired start and end point, and connect all of them via straight lines. If the points are close enough, the straight segments will not be visible and they will look like a curve.

So this is the proof of concept:

\documentclass{article}
\usepackage{tikz}
\begin{document}
\thispagestyle{empty}
\usetikzlibrary{calc}

\begin{tikzpicture}

  % First, define nodes
  \draw (0,0) node[circle, inner sep=0.8pt, fill=black, label={below:{$E$}}] (E) {};  
  \draw (5,0) node[circle, inner sep=0.8pt, fill=black, label={below:{$F$}}] (F) {}; 

  % Draw the curved line. No to[bend] is allowed, only explicit control points
  \draw  (E) .. controls +(1.9,1) and +(-1.9,1).. (F);
  % Now, repeat the same curve, shifted up, and define 20 inner points named
  % p1, p2, p3, etc.. for positions 0.15, 0.16, 0.17, etc up to 0.35 inside that curve
  \path  ($(E)+(0,0.2)$) .. controls +(1.9,1) and +(-1.9,1) ..  ($(F)+(0, 0.2)$)
     {\foreach \t [count=\i] in {0.15,0.16,...,0.35} {  coordinate[pos=\t] (p\i) } };

  % Finally draw the "curve" (polygonal line indeed) through those 20 inner points
  \draw[->>] (p1) { \foreach \i in {1,...,20} {-- (p\i) } };

  % A second example, with the same ideas but different path
  \draw[red]  (E) .. controls +(2,-2) and +(-3,-1).. (F);
  \path  ($(E)+(0,0.2)$) .. controls +(2,-2) and +(-3,-1)..  ($(F)+(0, 0.2)$) 
     {\foreach \t [count=\i] in {0.15,0.16,...,0.55} {  coordinate[pos=\t] (p\i) } };

  \draw[red, ->>] (p1) { \foreach \i in {1,...,40} {-- (p\i) } };
\end{tikzpicture}
\end{document}

This is the result:

Result

Update

A little refinement can be done in above code. Instead of specifying the foreach loop as from 0.15 to 0.55 in steps of 0.1, it looks more natural to simply specify the desired number of inner points (i.e: the "resolution" of the parallel curve), and let to some other expression to compute the pos for each of those points. This is done like this:

\path  ..curve specification..
     {\foreach \i in {1,...,40} {  coordinate[pos=0.15+0.55*\i/40] (p\i) } };

So, in this example, 40 intermediate points are computed, and the formula for the pos key specifies 0.15 and 0.55 as the extremes of the parallel curve.

However, this approach does not use a "true" parallel line. The curve used to compute the points is simply the same orginal curve, only shifted in the Y axis. This could work for smooth arcs which are mostly horizontal, but it will fail for parts of the curve where the tangent is not so horizontal. Consider for example:

\begin{tikzpicture}
  % First, define nodes
  \draw (0,0) node[circle, inner sep=0.8pt, fill=black, label={below:{$E$}}] (E) {};  
  \draw (5,2) node[circle, inner sep=0.8pt, fill=black, label={below:{$F$}}] (F) {}; 

  \draw[red]  (E) .. controls +(5,-3) and +(-4,1).. (F);
  \path  ($(E)+(0,0.2)$) .. controls +(5,-3) and +(-4,1)..  ($(F)+(0,0.2)$) 
     {\foreach \i in {1,...,40} {  coordinate[pos=0.15+0.75*\i/40] (p\i) } };

  \draw[blue, ->>] (p1) { \foreach \i in {1,...,40} {-- (p\i) } };
\end{tikzpicture}

This produces:

Bad Result

It can be seen that the distance between the red and blue lines is not constant. How can this be solved?

The following idea works: instead of shifting the curve, compute the sequence of points (p1), (p2) etc. on the original curve. But then, when drawing the parallel line, do not use those points, but points which are at a fixed distance (eg: 0.2cm) from each of these points in the direction perpendicular to the curve. This direction can be computed as the vector from current point (p_i) to next point (p_j), rotated 90 degrees.

Fortunately, tikz has the interpolated coordinates expression which allows for this with a simple formula: ($(p\i)!0.2cm!90:(p\j)$).

So, using this idea:

\begin{tikzpicture}
  % First, define nodes
  \draw (0,0) node[circle, inner sep=0.8pt, fill=black, label={below:{$E$}}] (E) {};  
  \draw (5,2) node[circle, inner sep=0.8pt, fill=black, label={below:{$F$}}] (F) {}; 

  % Draw curved path
  \draw[red]  (E) .. controls +(5,-3) and +(-4,1).. (F);
  % Compute points on the same curve
  \path  (E) .. controls +(5,-3) and +(-4,1)..  (F) 
     {\foreach \i in {1,...,40} {  coordinate[pos=0.15+0.75*\i/40] (p\i) } };
  % Draw parallel curve
  % (note that first and last points are specified out of the loop)
  \draw[blue, ->>] ($(p1)!.2cm!90:(p2)$) 
     { \foreach \i [count=\j from 3] in {2,...,39} {-- ($(p\i)!.2cm!90:(p\j)$) } }
     -- ($(p40)!.2cm!-90:(p39)$);
\end{tikzpicture}

And this gives a perfect result!

Result2

  • 1
    +1! I was thinking about using arc, which also enables the use of pos, but I guess control points is more general. – Herr K. Jun 11 '14 at 1:23
  • 1
    "I have no idea of the equations that should be solved to find those control points." There are none. A parallel displacement of a generic cubic bézier is not necessarily again a cubic bézier so the problem cannot be solved exactly (which is why lots of the solutions for finding parallel paths use workarounds such as yours or using double lines). – Loop Space Jun 11 '14 at 8:42
  • 1
    Cool :) But then why not use decorations? That's much simpler to follow and raise – percusse Jun 11 '14 at 8:42
  • 1
    @percusse Not sure about what do you mean "much simpler". Do you refer to the internal implementation of the decoration? Or to the API for the final user? If the second, I agree. My answer was only a "proof of concept" not very concerned with usability. If the first, I don't se how. Could you write an answer implementing your idea? – JLDiaz Jun 11 '14 at 11:29
  • 2
    +1 for the insight it gives. One (petty --- the code works so this is a minor detail) nitpick: 0.15+0.55*\i/40 with i in {1..40} would give pos points of 0.16375..0.7, or am I wrong? shouldn't be \i in {0,...,40}...0.15+(0.55-0.15)*\i/40 to have 41 points between 0.15 and 0.55? – Rmano Jun 12 '14 at 22:10
6

Using clipping and decorations, you can achieve the following. While the solution is (in my opinion) simpler, it ends up being lengthier to implement. The code sample is hacky around the edges, most notably when it comes to placing the coordinates of the intermediate points, and of the arrow.

Some explanations:

For the intermediate points, you can do it with pos, which requires, as in the other solution, to give up on the to path. Or you can compute them from your end points. The result is somewhat different: with pos, the result depends on the "speed" of the parametrization of the graph. With the end points, you control more thinly what you get (here the 0.15 and 0.35 ratios are taken on the x axis for example).

In both cases, you don't need precise values for these coordinates, but just enough to make a clipping box (drawn in red below). The fact that the curve is "horizontal" makes it easier, because we can just use a rectangle as the clipping path, but there would be workarounds in other cases too.

For the arrow, I used the solution described in this question, using the decoration library. This allows to set the arrow position on the path, making it easier with the pos placement for the clipping box. But in both cases, you need to tweak the position a little to make the arrow tip fit in the clipping box.

Example

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc,arrows,decorations.markings}
\tikzset{>=latex}
\begin{document}

% No absolute coordinates for the intermediate points, requires control points

\begin{tikzpicture}[x=1cm,y=1cm] % No idea why it's needed
  \draw (0,0) node[circle, inner sep=0.8pt, fill=black, label={below:{$C$}}] (C) {};  
  \draw (10,0) node[circle, inner sep=0.8pt, fill=black, label={below:{$D$}}] (D) {};  
  \draw (C) .. controls +(1.9,1) and +(-1.9,1).. (D)
        node [pos=0.15] (caux) {} node [pos=0.35] (daux) {};

  \begin{scope}[draw=red]
    \node (c) at ($(caux) + (0,0.5)$) {};
    \node (d) at ($(daux) + (0,0.5)$) {};
    \draw[clip] ($(c) - (0,0.2)$) rectangle ($(d) + (0,0.2)$); % Arbitrary offset
    \begin{scope}[draw=black]
      \draw[decoration={markings,mark=at position 0.325 with \arrow{>>}},postaction=decorate]
      (0,0.5) .. controls +(1.9,1) and +(-1.9,1)..  (10,0.5);
      \end{scope}
  \end{scope}
\end{tikzpicture}


% With arbitrary absolute coordinates

\begin{tikzpicture}[x=1cm,y=1cm]
  \draw (0,0) node[circle, inner sep=0.8pt, fill=black, label={below:{$C$}}] (C) {};  
  \draw (10,0) node[circle, inner sep=0.8pt, fill=black, label={below:{$D$}}] (D) {};  
  \draw (C) to [bend left=45] (D);

  \begin{scope}[draw=red]
    \node (c) at ($(C)!0.15!(D)$) {};
    \node (d) at ($(C)!0.35!(D) + (0,4)$) {};
    \draw[clip] ($(c) - (0,0.2)$) rectangle ($(d) + (0,0.2)$); % Arbitrary offset
    \begin{scope}[draw=black]
      \draw[decoration={markings,mark=at position 0.365 with \arrow{>>}},postaction=decorate]
      (0,0.5) to [bend left=45] (10,0.5);
    \end{scope}
  \end{scope}

\end{tikzpicture}

\end{document} 

With pos:

enter image description here

Without pos:

enter image description here

  • Thanks T. Verron! I had tried playing around with clipping, but hadn't gotten very far with it... Your solution works really well for the mostly horizontal curves, as proposed in my question, but comparing for method to JLDiaz's answer, I think it'll probably struggle with vertical curve sections (since your clipped arrow sections aren't shifted normally from the curved line segments). Correct me if I'm wrong? – Dominic Kerr Jun 11 '14 at 14:03
  • 1
    I am not sure to understand... The arrow itself is not clipped (well, it would be if it was going out of the box...). Instead it is placed manually at the relevant position, and this could be done on any curve. – T. Verron Jun 11 '14 at 14:11
  • 1
    There's no doubt @JLDiaz's answer will work wayyyy more generally than mine, though. ;) The only thing you gain with my answer is probably that you don't need to convert your path specifications to the syntax with control points. – T. Verron Jun 11 '14 at 14:13

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.