14

I have lots of curves, each of which I would like to decorate with an arrow precisely at the centre. Here is a simple attempt to do this with TikZ:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{decorations.markings}
\begin{document}
\tikz{\draw [decoration={markings,
    mark=at position 0.5 with \arrow{>}},
    postaction=decorate] (0,0) to (0.3,0);}
\end{document}

This produces the following result:

enter image description here

This looks bad: the arrow tip is at the half-way point, but I want the midpoint of the arrow at the half-way point.

How can I do this automatically with TikZ?

Of course in this example I could manually configure the position of the arrow. But that is not good enough, because I have arrows on lots of curves of different lengths. Also, in the real document I use the arrows.meta functionality of TikZ 3.0 to configure my arrows, so I would like the solution to be consistent with this.

The arrow length is a fixed quantity, so it would be ideal if there was a simple way to automatically 'nudge' the decoration by half the arrow length.

4
  • @LaRiFaRi this could cause problems if different arrow tips/sizes are used throughout. I'm working on a more general solution. :-) Jan 8, 2015 at 13:30
  • related: tex.stackexchange.com/q/4986
    – LaRiFaRi
    Jan 8, 2015 at 13:38
  • A general solution would be great, but for me it would be sufficient to have a solution that assumes all arrow tip lengths to be constant. Jan 8, 2015 at 13:50
  • @LaRiFaRi: thanks for spotting that related question. I'm hoping there's a 'modern' way to do it, with all the updated decoration stuff in TikZ 3.0. Jan 8, 2015 at 13:52

4 Answers 4

7

Something to start with... I am not able to measure the width of the arrow head. If you can, you might add that to my little macro:

% arara: pdflatex

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{decorations.markings}
\newcommand*{\halfway}{0.5*\pgfdecoratedpathlength+.5*3pt}

\begin{document}
    \tikz{\draw [decoration={markings,
        mark=at position \halfway with \arrow{>}},
        postaction=decorate] (0,0) to [out=30, in=150] (1,0);}
\end{document}
1
  • 1
    I'm choosing this answer due to its extreme simplicity. Thanks to everyone else for their great answers, especially @JLDiaz. Jan 12, 2015 at 16:42
6

(See update at the end for a general solution)

You can alter the definition of \pgf@lib@dec@arrowhead, which is the macro which computes and draws the arrow head when you use \arrow as decoration.

This is the original version of this macro (which appears in the file .../generic/pgf/libraries/decorations/pgflibrarydecorations.markings.code.tex):

\def\pgf@lib@dec@arrowhead#1#2{%
  \expandafter\ifx\csname tikz@special@arrow@end#2\endcsname\relax% be nice to TikZ
    \pgfsetarrowsend{#2}
  \else%
    \pgfsetarrowsend{\csname tikz@special@arrow@end#2\endcsname}%
  \fi%
  \pgf@x=0pt%
  \pgf@shorten@end%
  \pgftransformxshift{-\pgf@x}
  \pgftransformxscale{#1}
  \pgflowlevelsynccm%
  \pgflowlevelobj{}{\pgf@endarrow}%
}

This macro uses \pgf@short@end to compute somehow the amount to shift the arrow head, which stores the result in \pgf@x. Then it shifts the arrow head in the amount -\pgf@x, and this is why the arrow head appears "to the left" of the desired point.

This is how this macro places different arrowheads (>, latex and stealth). The red dot shows the center of the segment:

Default Placement

It looks like the shifting places the tip of the arrow near the red dot, so one could thing that removing that shifting the midpoint of the arrow will appear placed at the red dot. Unfortunately this depends of the kind of arrowhead used. This is the result with \pgftransformxshift{-\pgf@x} removed:

Without shifting

As you can see, the > arrow type is slightly pushed to the right, but not enough. However, the latex and stealth types are much more to the right (in fact, too much to the right). So there is no a universal way to alter \pgf@lib@dec@arrow@head to have the arrow head correctly centered, probably because each arrowhead has its "origin" at a different point.

If we concentrate only on the > type, we discover by trial and error that pushing the arrow the amount 1.5\pgf@x to its right, places it at the desired point. It works for different line widths too (which alter the arrow size), and apparently also on curves.

This is the code which implements this idea:

\documentclass[a6paper]{article}
\usepackage{tikz}
\usetikzlibrary{decorations.markings}
\usetikzlibrary{arrows}
\begin{document}

\makeatletter
\def\pgf@lib@dec@arrowhead#1#2{%
  \expandafter\ifx\csname tikz@special@arrow@end#2\endcsname\relax% be nice to TikZ
    \pgfsetarrowsend{#2}
  \else%
    \pgfsetarrowsend{\csname tikz@special@arrow@end#2\endcsname}%
  \fi%
  \pgf@x=0pt%
  \pgf@shorten@end%
  \pgftransformxshift{1.5\pgf@x}% <==== Modification
  \pgftransformxscale{#1}
  \pgflowlevelsynccm%
  \pgflowlevelobj{}{\pgf@endarrow}%
}
\makeatother
\tikz{\draw [decoration={markings,
    mark=at position 0.5 with \arrow{>}},
    postaction=decorate] (0,0) to (0.3,0);
    \fill[red] (0.15, 0) circle (.1mm);
}

\tikz{\draw [very thin,decoration={markings,
    mark=at position 0.5 with \arrow{>}},
    postaction=decorate] (0,0) to (0.3,0);
    \fill[red] (0.15, 0) circle (.1mm);
}


\tikz{\draw [very thick,decoration={markings,
    mark=at position 0.5 with \arrow{>}},
    postaction=decorate] (0,0) to (0.3,0);
    \fill[red] (0.15, 0) circle (.1mm);
}


\tikz{\draw [decoration={markings,
    mark=at position 0.5 with \arrow{>}},
postaction=decorate] (0,0) to[out=90, in=90] (0.3,0);
    \fill[red] (0.15, 0) circle (.1mm);
}
\end{document}

And this is the result:

Optimal shifting for the > type

Update: More experiments and a general (?) solution

Tikz arrows are drawn by code which assumes an origin located at the end of a line, and pointing to the right (pgf shifts and rotates conveniently this base arrow shape). Usually the arrow "protrudes" to its right from that "origin" point, but this depends on the particular arrow tip. For example, stealth tip protrudes approximately half of the arrowhead size, while to shape protrudes almost zero.

In order to avoid the arrowhead to extend past the line end, each arrow shape provides macros to define "how much" they protrude to the right. Tikz uses this amount to shorten the line, so that the arrow tip ends at the intended point. It also provides macros to define how much the arrowhead extends to its left. This is needed when drawing the arrowhead "reversed".

These macros can be useful to us. Calling the first one, we find how much the arrowhead extends to the right (call this R), and with the second one we find how much it extends to the left (call this L). So we can compute the total size of the arrowhead as R+L, and also the required shifting to align its midpoint with the coordinate origin (this would be (L-R)/2).

Unfortunately, depending on the arrow shape, the geometrical center of the arrowhead is not always the one we perceive as its optical center (due for example to the asymetrical mass distribution in the x direction), so even accurately computing the geometrical center of the arrow we can end with one arrow head which does not look centered.

In order to test diferent methods of shifting the arrowhead and see the result for different arrow shapes and line widths, I wrote the following code.

\def\testarrows{
    \begin{tikzpicture}[transform canvas={scale=2}]
    \foreach[count=\j] \arrowtip in {to,stealth, triangle 45} {
        \foreach[count=\i] \width in {0.2, 0.4, 0.8} {
            \coordinate (A) at (\i/2, -\j/4);
            \coordinate (B) at ($(A)+(0.3,0)$);
          \draw [decoration={markings, mark=at position 0.5 with \arrow{\arrowtip}},
              postaction=decorate, line width=\width] (A) -- (B);
              \fill[red] ($(A)!.5!(B)$) circle (.1mm);
        }
    }
    \end{tikzpicture}\vskip 2.5cm
}

When run this code produces the standard arrows, centered at their tip (because they are shifted the amount -R by the \arrow{} decoration):

Standard positioning

If we remove the -R shifting made by the standard library, we get each arrow centered at its origin. We can see how the position of the origin varies greatly for each arrow shape:

Centered at origin

If we center at the geometrical center of each arrow (by shifting the amount (L-R)/2 we get:

Geometrically centered

We can see that the result for to and stealth shapes is very good, but triangle 45 looks like it is slightly shifted to its left. This is caused by the sharp tip, which is thinner than the line in which it is drawn.

This is the code used to generate these images. The part which computes R is \expandafter\csname pgf@arrow@right#2\endcsname%, and the one which computes L is \expandafter\csname pgf@arrow@left#2\endcsname%. Both leave the result in \pgf@x, which must be zero before calling them. I had to use also \pgf@y as auxiliar variable to compute (L-R)/2.

\documentclass{article}
\usepackage{nopageno}
\usepackage{tikz}
\usetikzlibrary{decorations.markings}
\usetikzlibrary{arrows,calc}

\makeatletter
\def\CenterOrigin#1#2{% (no shifting)
  \expandafter\ifx\csname tikz@special@arrow@end#2\endcsname\relax% be nice to TikZ
    \pgfsetarrowsend{#2}
  \else%
    \pgfsetarrowsend{\csname tikz@special@arrow@end#2\endcsname}%
  \fi%
  \pgftransformxscale{#1}
  \pgflowlevelsynccm%
  \pgflowlevelobj{}{\pgf@endarrow}%
}
\def\CenterMiddlePoint#1#2{%
  \expandafter\ifx\csname tikz@special@arrow@end#2\endcsname\relax% be nice to TikZ
    \pgfsetarrowsend{#2}
  \else%
    \pgfsetarrowsend{\csname tikz@special@arrow@end#2\endcsname}%
  \fi%
  \pgf@x=0pt\pgf@y=0pt%
  \expandafter\csname pgf@arrow@left@#2\endcsname%
  \pgf@y=\pgf@x\pgf@x=0pt%
  \expandafter\csname pgf@arrow@right@#2\endcsname%
  \pgftransformxshift{0.5\[email protected]\pgf@x}%
  \pgftransformxscale{#1}
  \pgflowlevelsynccm%
  \pgflowlevelobj{}{\pgf@endarrow}%
}
\makeatother

\def\testarrows{
    \begin{tikzpicture}[transform canvas={scale=2}]
    \foreach[count=\j] \arrowtip in {to,stealth, triangle 45} {
        \foreach[count=\i] \width in {0.2, 0.4, 0.8} {
            \coordinate (A) at (\i/2, -\j/4);
            \coordinate (B) at ($(A)+(0.3,0)$);
          \draw [decoration={markings, mark=at position 0.5 with \arrow{\arrowtip}},
              postaction=decorate, line width=\width] (A) -- (B);
              \fill[red] ($(A)!.5!(B)$) circle (.1mm);
        }
    }
    \end{tikzpicture}\vskip 2.5cm
}

\begin{document}
Original arrows (arrow tip centered)\par
\testarrows

Unshifted (arrow "origin" centered)\par
\makeatletter\let\pgf@lib@dec@arrowhead=\CenterOrigin\makeatother
\testarrows

Middle of arrow shape centered\par
\makeatletter\let\pgf@lib@dec@arrowhead=\CenterMiddlePoint\makeatother
\testarrows
\end{document}
2
  • Is it not possible to use \pgfarrowssettipend here?
    – user43963
    Jan 9, 2015 at 0:04
  • @user43963 \pgfarrowsettipend is not in my version of tikz/pgf (1.11, 2008), and it provides only the "right extent" and not the "left extent" anyway. But based on your comment I discovered a new approach (see update). Thank you!
    – JLDiaz
    Jan 9, 2015 at 10:38
4

The older solutions here have the problem that they either don't work on curves (the tip has to be adjusted tangentially, not along the curve) or that they don't work anymore because the code of the underlying library has changed. Fortunately, the library has become more flexible now by processing the option xshift of the \arrow command before positioning the arrow tip along the path.

By using \arrow[xshift=2pt] instead of \arrow the reference point of the arrow moves from the tip to the center of the arrow. 2pt is half of the length of the arrow that is defined as Latex[length=4pt].

\documentclass[border=2mm]{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations.markings,arrows.meta}
\tikzset
  {midarrow/.style={decoration={markings,mark=at position 0.5 with
     {\arrow[xshift=2pt]{Latex[length=4pt,#1]}}},postaction={decorate}}
  }
\begin{document}
\begin{tikzpicture}
  \draw[midarrow] (0,0) -- (1,0);
  \draw[midarrow={open,fill=white}] (0,0) arc(-90:180:10pt);
\end{tikzpicture}
\end{document}

enter image description here

3

I was hoping that putting the arrow in a node will allow me to center it, but unfortunately the arrow is "overlayed" as we can see here.

\documentclass[varwidth,border=1mm]{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations.markings}
\begin{document}
\tikz{\draw [decoration={markings,
    mark=at position 0.5 with {
      \node[circle, fill=red, inner sep=.1pt]{
        \tikz\arrow{>};
      };
    }},
    postaction=decorate] (0,0) to (0.3,0);}
\end{document}

enter image description here

An ugly solution will be to preset the shift for any type of arrow that you use.

\documentclass[varwidth,border=1mm,convert={density=3500}]{standalone}
\usepackage{tikz}
\usetikzlibrary{decorations.markings}
\usetikzlibrary{arrows.meta}
\tikzset{
  test/.style={ decoration={markings,
                  mark=at position 0.5 with {#1;\fill[red] circle(.2pt);}},
                postaction=decorate}
}
% manually defined shifts for some arrow types
\def\Latexarrow{{\arrow[xshift={2pt + 2.25\pgflinewidth}]{Latex}}}
\def\ellipsearrow{{\arrow[xshift=1.65pt +2.47\pgflinewidth]{Ellipse}}}
\def\Stealtharrow{{\arrow[xshift=1.5pt+2.25\pgflinewidth]{Stealth}}}

\begin{document}
  \begin{tikzpicture}
    \draw [opacity=.7,ultra thick,test=\Latexarrow] (0,0) to (0.5,0);
    \draw [opacity=.7,ultra thick,test=\ellipsearrow] (0,0.5) to (0.5,0.5);
    \draw [opacity=.7,line width=1pt,test=\Stealtharrow] (0,1) to (0.5,1);
  \end{tikzpicture}
\end{document}

enter image description here

How to do this:

  1. go to the file pgflibraryarrows.meta.code.tex;
  2. search for your arrow, for example Stealth;
  3. when you find it, look for the value of length, in this case length = +3pt 4.5 .8;
  4. set your shift to the half of this length, here half of 3pt 4.5, so to 1.5pt+2.25\pgflinewidth.
2
  • In you first node approach, have you tried with anchor=west?
    – user43963
    Jan 8, 2015 at 23:59
  • @user43963 putting anchor=west will just shift with the radius of the red circle. This is not what we want. The reason that using this approach is hopeless is that the arrow is "overlayed" (it goes out of the containing node). So the node doesn't know about the size of the arrow.
    – Kpym
    Jan 9, 2015 at 7:48

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .