[Tex/LaTex] Placement of arrowtips with TikZ’s markings

decorationstikz-pgf

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)?

Best Answer

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...)