20

With the decoration library it is possible to place arrow tips at arbitrary positions along a path. TikZ places the front end of the arrow at the specified position, which is often exactly what one wants. However, sometimes I’d find it would look better if the middle of the arrow tip is placed at the specified position, in particular when centering an arrow an a line:

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{decorations.markings,arrows}

\begin{document}

\begin{tikzpicture}[
    decoration={markings,mark=at position 0.5 with {\arrow{triangle 60}}},
    ]
    \draw[postaction={decorate}] (0,0.2) -- (1,0.2);
    \draw[postaction={decorate}] (1,-0.2) -- (0,-0.2);
\end{tikzpicture}

\end{document}

gives

what I want

instead of

what I want

I produced the second picture by guessing where the front end of the arrow should be to have the center of the arrow in the middle of the path.

Is there a way to have TikZ position the arrows this way (without having to guess the positions)?

3
  • Another great TikZ question. Are you in touch with the package authors? Commented Nov 5, 2010 at 22:48
  • No. Should I be?
    – Caramdir
    Commented Nov 5, 2010 at 23:08
  • 3
    Actually, I would really like the package authors to be here on SE. Just saying that This qn of yours and other other one about the snakes with arrows in the middle look like areas for enhancing TikZ. Commented Nov 5, 2010 at 23:25

2 Answers 2

7

A first step to better arrow placement would be to be able to move arrows along the path by some given distance, e.g. to say something like place the arrow 3pt left of the middle of the path.

So I started to look at the code in pgflibrarydecorations.markings. The posititon calculation is done by \pgf@lib@dec@parsenum. At first I thought I could simply put a wrapper around that macro that splits any input with + into the summands, calculates the lengths of each separately and then adds them together. Unfortunately this doesn't work: the macro subtracts negative values form the total length, so that 0.5-3pt would become 0.5*(total length) + (total length) - 3pt. So I rewrote the whole macro.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{decorations.markings}

\makeatletter
% overwrite the number parsing macro from pgflibrarydecorations.markings
\def\pgf@lib@dec@parsenum#1{%
    \gdef\pgf@lib@dec@computed@width{0 pt}%
    \tsx@pgf@lib@dec@parsenum#1+endmarker+%
    \ifdim\pgf@lib@dec@computed@width<0pt\relax%
        \pgfmathparse{\pgfdecoratedpathlength\pgf@lib@dec@computed@width}
        \edef\pgf@lib@dec@computed@width{\pgfmathresult pt}%
    \fi%
}

\def\tsx@pgf@lib@dec@parsenum@endmarker{endmarker}

% this is iterated over all numbers that are summed
\def\tsx@pgf@lib@dec@parsenum#1+{
    \def\temp{#1}%
    \ifx\temp\tsx@pgf@lib@dec@parsenum@endmarker%
    \else%
        \tsx@pgf@lib@dec@parsenum@one{#1}%
        \expandafter\tsx@pgf@lib@dec@parsenum%
    \fi%
}

% calculate the length for each number
\def\tsx@pgf@lib@dec@parsenum@one#1{%
  \pgfmathparse{#1}%
  \ifpgfmathunitsdeclared%
    \pgfmathparse{\pgf@lib@dec@computed@width + \pgfmathresult pt}%
  \else%
    \pgfmathparse{\pgf@lib@dec@computed@width + \pgfmathresult*\pgfdecoratedpathlength*1pt}%
  \fi%
  \edef\pgf@lib@dec@computed@width{\pgfmathresult pt}%
}
\makeatother

\begin{document}
\begin{tikzpicture}
    \draw[decoration={
        markings,
        mark={at position 0.5 + -1cm with {\arrow{>}}},
        },
        postaction={decorate}
        ]
        (0,0) to [out=30, in=150] (4,0);
\end{tikzpicture}
\end{document}

example

This is my first time writing this type of iterative macro. So maybe someone could tell me if this is the 'right' way to do this. Also, is there an easy way to allow both plus and minus in the input, e.g. 0.5-1cm instead of 0.5+-1cm? (I know, I shouldn't put a question into an answer...)

1
5

You can always draw the arrow yourself, instead of using the "arrows" library:

\begin{tikzpicture}[
  decoration={markings,mark=at position 0.5 with {\fill (2pt,0)--(-2pt,2.31pt)--(-2pt,-2.31pt)--cycle;}},
   ]
   \draw[postaction={decorate}] (0,0.2) -- (1,0.2);
   \draw[postaction={decorate}] (1,-0.2) -- (0,-0.2);
\end{tikzpicture}

The mark you get when you use a predefined arrow has tip at (0,0). This mark will be a triangle centered at (0,0).

Of course, it will not work that great if your path is curved. The arrow tip will probably not be on the path.

2
  • How could I recreated the other arrow types (I only used the triangle in the example because it is big, so that the difference is easy to see)?
    – Caramdir
    Commented Nov 6, 2010 at 16:13
  • The problem with curved paths is a good point and is probably the reason why there is no option to center at the center of the arrow. Maybe it is possible to change TikZ in such a way that it accepts pos=0.5+2pt? Then one would only need to guess the correction factor once, instead of for every path separately.
    – Caramdir
    Commented Nov 6, 2010 at 16:17

You must log in to answer this question.

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