2

I am drawing curved arrows in tikz-cd, using the method devised by @AndréC in this answer. There are situations where I want to shorten these arrows, proportional to their arc length. However, the naïve solution (using shorten) is not suitable, as it creates distorted curves (as discussed, for instance, in this question). As an example (Example 1), the following two curves have the same input path, but the red curve has been shortened. What should happen is the red curve should overlay the black curve.

bad shortened curve

Note that it is not just the position of the shortened curve that is incorrect: the shape is incorrect too: in the example (Example 2) below, I've positioned the curves so they have the same apex, but the shortened curve does not overlay the original (the amount of distortion depends on the curve width and height).

overlaid bad curve

The other issue with shorten is that it requires an absolute length to shorten by, whereas I want to specify it proportionally with respect to the length of the curve itself.

I attempted to manually draw an arrow head with decorations.markings, but this does not properly calculate the right size for the arrowhead (or take into account the existing style of the head or tail). In the example below (Example 3), the arrow head further up the curve should be the same size as the one at the tip.

bad manual head

My idea was to shorten this arrow using a custom dash pattern, but this seems like a hack that is likely in practice to fail in edge cases. Ideally, the curve path itself should be changed, which would handle the existing style/arrowheads/tails/etc. The option pgfpathcurvebetweentime seemed like a possible solution, but I could not figure out how to integrate it with the custom curve style in the two examples.

How might one add an option to the custom curve style to allow the curve to be shortened (e.g. curve={height=-40pt, shorten=0.2})? In practice, shortening symmetrically from both ends is usually sufficient, but having options to shorten the start and end differently would be even better if it doesn't add much extra complexity.

For a demonstration of what I intend by "shortening a curved arrow", see @Thruston's example.

Example 1

\documentclass{article}
\usepackage{tikz-cd}
\usetikzlibrary{calc}

\begin{document}

{\tikzset{curve/.style={settings={#1},to path={(\tikztostart)
    .. controls ($(\tikztostart)!\pv{pos}!(\tikztotarget)!\pv{height}!270:(\tikztotarget)$)
    and ($(\tikztostart)!1-\pv{pos}!(\tikztotarget)!\pv{height}!270:(\tikztotarget)$)
    .. (\tikztotarget)\tikztonodes}},
    settings/.code={\tikzset{quiver/.cd,#1}
        \def\pv##1{\pgfkeysvalueof{/tikz/quiver/##1}}},
    quiver/.cd,pos/.initial=0.35,height/.initial=0}

\[\begin{tikzcd}
    {\bullet} & {\bullet}
    \arrow[from=1-1, to=1-2, curve={height=-40pt}, shorten <= 30pt, shorten >= 30pt, color=red]
    \arrow[from=1-1, to=1-2, curve={height=-40pt}]
\end{tikzcd}\]}

\end{document}

Example 2

\documentclass{article}
\usepackage{tikz-cd}
\usetikzlibrary{calc}

\begin{document}

{\tikzset{curve/.style={settings={#1},to path={(\tikztostart)
    .. controls ($(\tikztostart)!\pv{pos}!(\tikztotarget)!\pv{height}!270:(\tikztotarget)$)
    and ($(\tikztostart)!1-\pv{pos}!(\tikztotarget)!\pv{height}!270:(\tikztotarget)$)
    .. (\tikztotarget)\tikztonodes}},
    settings/.code={\tikzset{quiver/.cd,#1}
        \def\pv##1{\pgfkeysvalueof{/tikz/quiver/##1}}},
    quiver/.cd,pos/.initial=0.35,height/.initial=0}

\[\begin{tikzcd}
    {\bullet} &&& {\bullet}
    \arrow[from=1-1, to=1-4, curve={height=-60pt}, shorten <= 30pt, shorten >= 30pt, color=red, yshift=-11pt]
    \arrow[from=1-1, to=1-4, curve={height=-60pt}]
\end{tikzcd}\]}

\end{document}

Example 3

\documentclass{article}
\usepackage{tikz-cd}
\usetikzlibrary{calc}
\usetikzlibrary{decorations.markings}

\begin{document}

{\tikzset{curve/.style={settings={#1},to path={(\tikztostart)
    .. controls ($(\tikztostart)!\pv{pos}!(\tikztotarget)!\pv{height}!270:(\tikztotarget)$)
    and ($(\tikztostart)!1-\pv{pos}!(\tikztotarget)!\pv{height}!270:(\tikztotarget)$)
    .. (\tikztotarget)\tikztonodes},
    % Arrow head and tail
    decoration={
      markings,
      mark=at position 0.8 with {\arrow{>}}}
    , postaction={decorate}},
    %
    settings/.code={\tikzset{quiver/.cd,#1}
        \def\pv##1{\pgfkeysvalueof{/tikz/quiver/##1}}},
    quiver/.cd,pos/.initial=0.35,height/.initial=0}

\[\begin{tikzcd}
    {\bullet} & {\bullet}
    \arrow[Rightarrow, from=1-1, to=1-2, curve={height=-40pt}]
\end{tikzcd}\]}

\end{document}
7
  • Isn't it enough to move this arrow with e.g. yshift=5pt? – AndréC Oct 28 '20 at 12:02
  • @AndréC: unfortunately not: the actual curve shape is incorrect. I've added an example in the question. In some cases, it's close enough not to be noticeable, but that's not always the case. I also don't know of a way to deterministically calculate how much to shift by to align the apices, even if this was sufficient. – varkor Oct 28 '20 at 12:25
  • What are the cases that prevent you from using such a move? – AndréC Oct 28 '20 at 12:44
  • I'm outputting TikZ code from a diagram editor, so the TikZ diagram needs to match the original diagram as closely as possible, for arbitrary diagrams. This means I can't hard-code offsets, and need to be confident that the output will be reasonable regardless of the shape of the curve. – varkor Oct 28 '20 at 13:22
  • I can add a parameter that shifts the curve perpendicular to the path, would that be okay with you? – AndréC Oct 28 '20 at 14:32
4

Is this what you mean?

enter image description here

This is done in Metapost, purely for comparison, and in the hope that some one else here can show us how to do the equivalent in TikZ.

\documentclass[border=5mm]{standalone}
\usepackage{luamplib}
\begin{document}
\mplibtextextlabel{enable}
\begin{mplibcode}
input colorbrewer-rgb
beginfig(1);
    z0 = -z1 = 34 left;
    path a; a = z0 .. controls (-13, 55) and (13, 55) .. z1;
    ahangle := 30;
    ahlength := 2;
    for s=1 upto 8:
        drawarrow a 
            cutbefore fullcircle scaled 10s shifted z0
            cutafter  fullcircle scaled 10s shifted z1
            withcolor Blues[9][s];
    endfor
    drawdot z0 withpen pencircle scaled dotlabeldiam;
    drawdot z1 withpen pencircle scaled dotlabeldiam;
endfig;
\end{mplibcode}
\end{document}

(Compile with lualatex).

What I have done each is draw the same curve each time, but with more of it "cut off" at each end.

3
  • 1
    + 1 very nice ! – AndréC Oct 29 '20 at 12:23
  • Yes, thank you, this is precisely what I mean. I will add a link to your answer in the original question for clarity. – varkor Oct 29 '20 at 13:12
  • Great answer...Thruston and don't forget that I'm still waiting for your answer on the feymmp package arrows. Otherwise I won't vote for you any more ahahah. – Sebastiano Oct 29 '20 at 22:13
3

This is a topic I also struggle with for some time. I found something that could be useful on this topic: Tikz Graphics: Curved arrow drawn parallel to curved line

I adapted it to the current question and am almost happy with it:

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

\begin{tikzpicture}

    % Nodes for start and end
    \node at (0,0) (A) {};  
    \node at (5,0) (B) {};  

  
    % Path from start to end, using Bezier definition (no to[bend] allowed)
    % The path is cut into 20 parts, using 'pos'
    \path  (A) .. controls +(2,4) and +(-2,4).. (B)
        {\foreach \t [count=\i] in {0.05,0.10,0.15,...,0.85,0.90,0.95,1}
            {
            coordinate[pos=\t] (p\i)
            }
        };
  
    % naming p0 and p20 to allow the loop to draw the complete first curve
    \coordinate (p0) at (A);
    \coordinate (p20) at (B);
    
    % Drawing the 9 other curves
    \foreach \k in {0,...,9}
        {
        \pgfmathtruncatemacro\l{20-\k}      % p\k is the start point, p\l is the end point
        \pgfmathtruncatemacro\clr{10*\k}    % fading the colour of the curves
        
        % Now draw (segments) between all p\i points from p\k to p\l
        \draw[cap=round,line width=1.5pt,-stealth,blue!\clr] (p\k)
            {\foreach \i in {\k,...,\l} {-- (p\i) }};
        }
    
\end{tikzpicture}
\end{document}

First attempt

Now, the main issue of this solution is that it doesn't draw curves but broken lines. This could easily be fixed by computing more points on the original path. You may want to play with it, while finding a better solution.

3
  • Thanks for sharing this method! This achieves the effect I was going for, but I'm concerned that it may not be compatible with existing TikZ/tikz-cd arrow styles, like double arrows and squiggly arrows, because it relies upon manually drawing the edges. Perhaps I'm wrong, though? – varkor Jan 2 at 1:18
  • I don't understand why it wouldn't be compatible with any TikZ decorations. You may use the decorations.markings TikZ library with postaction to place any arrow tip at the end of the partial path. I'm working on another better solution, maybe you could be more precise about what you exactly want to draw. – SebGlav Jan 2 at 11:28
  • I'm just not familiar enough with tikzpicture to know how to apply the Rightarrow and squiggly styles, for instance, to arrows drawn with this method. – varkor Jan 2 at 12:18
2

Here is my proposal:

On the previous style, I added the shift parameter which allows me to shift the curve perpendicular to the path. To do this, I applied the idea I developed in my answer to the question Annotating a table with arrows.

The new style is called curve 1, I put as default height=-40 pt as in your examples.

screenshot

\documentclass{article}
\usepackage{tikz-cd}
\usetikzlibrary{calc}

\begin{document}

\tikzset{curve 1/.style={settings={#1},
                        to path={($(\tikztostart)!\pv{shift}!90:(\tikztotarget)$) .. controls ($(\tikztostart)!\pv{pos}!(\tikztotarget)!\pv{height}!270:(\tikztotarget)$)
    and ($(\tikztostart)!1-\pv{pos}!(\tikztotarget)!\pv{height}!270:(\tikztotarget)$)
    .. ($(\tikztotarget)!\pv{shift}!270:(\tikztostart)$)\tikztonodes}},
    settings/.code={\tikzset{quiver/.cd,#1}
        \def\pv##1{\pgfkeysvalueof{/tikz/quiver/##1}}},
    quiver/.cd,pos/.initial=0.35,height/.initial=-40pt,shift/.initial=0pt}
    
\[\begin{tikzcd}
    {\bullet} & {\bullet}
    \arrow[from=1-1, to=1-2, curve 1={shift=2mm},color=blue,thick]
    \arrow[from=1-1, to=1-2, curve 1={shift=4mm},color=cyan,thick]
    \arrow[from=1-1, to=1-2, curve 1={shift=6mm},color=green,thick]
    \arrow[from=1-1, to=1-2, curve 1={shift=8mm},color=yellow,thick]
    \arrow[from=1-1, to=1-2, curve 1={shift=10mm},color=orange,thick]
    \arrow[from=1-1, to=1-2, curve 1={shift=12mm},color=red,thick]
\end{tikzcd}\]

\end{document}
1
  • Thanks for your answer. Unfortunately, this doesn't solve the problem mentioned above, that using shorten, combined with shift will have distorted shape compared to the original curve. Also, even if the shape were correct, there's no well to tell how much to shift by to align the two curves perfectly. – varkor Oct 28 '20 at 18:17
0

Since I don't really understand what still refrains you to use this method, I made some adjustments, in order to make it easier to use. In this new version, you can decide the position of both starting and ending points of your arrow, and the number of points to be computed, in order to avoid the uggly broken line effect on strongly curved lines.

\documentclass{standalone}
\usepackage{tikz}
\begin{document}
\thispagestyle{empty}

\begin{tikzpicture}

    % First, the starting and ending nodes
    \draw (0,0) node[circle, inner sep=0.8pt, fill=black, label={below:{$A$}}] (A) {};  
    \draw (5,1) node[circle, inner sep=0.8pt, fill=black, label={above:{$B$}}] (B) {};
    
    % Define the path between the two nodes (Bezier curve)
    \def\route{(A) .. controls +(2,5) and +(-2,-3).. (B)}
    
    % Drawing the complete path
    \draw[orange] \route;
    
    %%%%%%%%%%%%%%
    % Blue arrow %
    %%%%%%%%%%%%%%
    
    \def\start{0.5}     % Starting point position
    \def\len{0.45}      % Arrow length (percentage of total line length)
    \def\numpoints{30}  % Number of points to draw segments between them
    
    % Computing the 30 points from the arrow start and the arrow end
    \path  \route 
        {\foreach \i in {1,...,\numpoints} { coordinate[pos=\start+\len*\i/\numpoints] (p\i) } };
        
    % Drawing a broken line linking all 30 points, with the arrow decoration at the end
    \draw[blue, cap=round, opacity=0.5, line width=1.5pt,->>] (p1)
        { \foreach \j in {2,...,\numpoints} {-- (p\j) } };
    
    %%%%%%%%%%%%%
    % Red arrow %
    %%%%%%%%%%%%%
    \def\start{0.4} \def\len{-0.3} \def\numpoints{30}
    \path  \route 
        {\foreach \i in {1,...,\numpoints} { coordinate[pos=\start+\len*\i/\numpoints] (p\i) } };
    \draw[red, cap=round, opacity=0.5, line width=1.5pt,->>] (p1)
        { \foreach \j in {2,...,\numpoints} {-- (p\j) } };
    
\end{tikzpicture}


\end{document}

Hope this will give you a better insight on this.Arrows on Curve

edit: I realize now that you would want to do this, using @AndréC 's method of creating curves between points. If you don't find how to implement this, let us know, sure someone will find a way.

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.