Decoration at end of line after “shorten >” command

I have a style that puts an arrow at the end of a line for me:

\makeatletter
\tikzset{nomorepostaction/.code={\let\tikz@postactions\pgfutil@empty}}
\makeatother
\tikzset{mystyle/.style={
shorten >=0.5pt,
postaction={
nomorepostaction,
decorate,
decoration={
markings,
mark=at position 1 with {\arrow[semithick]{>}}
}
}
}}


I need to do it like this as it's exactly this "semithick" arrowhead that I want. But if I shorten a line, the arrowhead is drawn in the wrong place:

\draw [mystyle, shorten >=5pt] (0,0) to (2,0);


It seems to me that the problem is caused by the "at position 1" part, which refers to the end of the line without taking into effect any shortening.

So, my question is: how can I define a style to reliably put a decoration at the end of a line (or some fixed offset from the end of the line), which is robust against possible shortening or lengthening of the line arising from "shorten <" and "shorten >"?

-
Not really an answer to the actual question, but wouldn't it be easier simply to define a new arrowhead? –  Loop Space Aug 16 '11 at 11:16
Maybe, but I like the standard semithick arrow - I don't want to have to design an arrowhead from scratch. But maybe I can use the standard semithick arrowhead directly in the new arrowhead definition... let me see if I can figure it out. –  Jamie Vicary Aug 16 '11 at 11:36
If all else fails, you can just copy the code for the standard arrowhead and adjust the linewidth calculation to get the desired width. Another approach is to adapt the standard arrow declaration to take a parameter ... I think I did that somewhere, I'll look it up. –  Loop Space Aug 16 '11 at 11:43
Yes, here it is: tex.stackexchange.com/questions/10980/… –  Loop Space Aug 16 '11 at 11:45

Here's an answer to the actual question (I still think that defining a new arrowhead would be more elegant). This uses Caramdir's answer to Placement of arrowtips with TikZ’s markings which extends the mark=at position to be able to handle arithmetic. That, together with the knowledge that shortenings are held in the lengths \pgf@shorten@end@additional (and \pgf@shorten@start@additional) seems to do the trick (at least with this example, I don't know if it would more generally).

Incidentally, the shorten > key is not cumulative so the shorten >=0.5pt in the mystyle decoration gets overriden by the shorten >=5pt in your actual usage. I wasn't sure if you wanted the arrowhead to be pulled back by an additional 0.5pt to whatever the line was already shortened by so I've given you various alternatives in the below. Hopefully there's enough there for you to figure out what fits your actual usage best.

\documentclass[border=10]{standalone}
% \url{http://tex.stackexchange.com/q/25830/86}
\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}%
}

\tikzset{nomorepostaction/.code={\let\tikz@postactions\pgfutil@empty}}
\tikzset{mystyle 1/.style={
shorten >=0.5pt,
postaction={
nomorepostaction,
decorate,
decoration={
markings,
mark=at position 1 with {\arrow[semithick]{>}}
}
}
},
mystyle 2/.style={
postaction={
nomorepostaction,
decorate,
decoration={
markings,
mark={at position 1 + - \pgf@shorten@end@additional with {\arrow[semithick]{>}}}
}
}
},
mystyle 3/.style={
postaction={
nomorepostaction,
decorate,
decoration={
markings,
mark={at position 1 + - \pgf@shorten@end@additional + -0.5pt with {\arrow[semithick]{>}}}
}
}
},
mystyle 4/.style={
shorten >=0.5pt,
postaction={
nomorepostaction,
decorate,
decoration={
markings,
mark={at position 1 + - \pgf@shorten@end@additional with {\arrow[semithick]{>}}}
}
}
},
}
\makeatother

\begin{document}
\begin{tikzpicture}
\foreach \st in {1,...,4} {
\begin{scope}[yshift=-\st cm]
\draw [mystyle \st,shorten >=5pt] (0,0) to (2,0);
\draw [mystyle \st] (0,-.5) to (2,-.5);
\end{scope}
}
\end{tikzpicture}
\end{document}


Result:

(And if you find this useful, go and vote for Caramdir's code in the link above! All I did was cut-and-paste.)

-
This is excellent, thanks Andrew. –  Jamie Vicary Aug 16 '11 at 11:36

Here's a new thick arrow. It's just the normal arrow definition taken from pgfcorearrows.code.tex, with the line \pgfsetlinewidth{...} added.

Edit: Following Andrew Stacey's suggestions, here's the same arrow with the thickness as an option. If you don't specify a unit for the thickness, the value will be multiplied by the line thickness to get the thickness for the arrow. The line thickness used is the one in force when the arrow is drawn.

Issuing

\draw [thick arrow] (0,0) to (2,0);
\draw [thick arrow=2pt] (0,-1) to (2,-1);
\draw [line width=2pt,thick arrow=1,shorten >=5pt] (0,-2) to (2,-2);
\draw [line width=2pt,thick arrow=2,shorten >=5pt] (0,-3) to (2,-3);


will yield

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

\makeatletter
\pgfarrowsdeclare{thickarrow}{thickarrow}
{
\pgfutil@tempdima=-0.84pt%
\pgfutil@tempdimb=0.21pt%
\pgfarrowsleftextend{+\pgfutil@tempdima}
\pgfarrowsrightextend{+\pgfutil@tempdimb}
}
{
\pgfmathparse{\pgfgetarrowoptions{thickarrow}}%
\ifpgfmathunitsdeclared%
\pgfmathparse{\pgfmathresult pt}%
\else%
\pgfmathparse{\pgfmathresult*\pgflinewidth}%
\fi%
\let\thickness=\pgfmathresult
\pgfsetlinewidth{\thickness pt}
\pgfutil@tempdima=0.28pt%
\pgfsetlinewidth{0.8\pgflinewidth}
\pgfsetdash{}{+0pt}
\pgfsetroundcap
\pgfsetroundjoin
\pgfpathmoveto{\pgfqpoint{-3\pgfutil@tempdima}{4\pgfutil@tempdima}}
\pgfpathcurveto
{\pgfqpoint{-2.75\pgfutil@tempdima}{2.5\pgfutil@tempdima}}
{\pgfqpoint{0pt}{0.25\pgfutil@tempdima}}
{\pgfqpoint{0.75\pgfutil@tempdima}{0pt}}
\pgfpathcurveto
{\pgfqpoint{0pt}{-0.25\pgfutil@tempdima}}
{\pgfqpoint{-2.75\pgfutil@tempdima}{-2.5\pgfutil@tempdima}}
{\pgfqpoint{-3\pgfutil@tempdima}{-4\pgfutil@tempdima}}
\pgfusepathqstroke
}
\makeatother

\tikzset{
set thickarrow thickness/.code={
\pgfsetarrowoptions{thickarrow}{#1}},
thick arrow/.style={set thickarrow thickness=#1,-thickarrow},
thick arrow/.default=0.6pt
}

\begin{document}

\begin{tikzpicture}[transform canvas={scale=3}]
\draw [thick arrow] (0,0) to (2,0);
\draw [thick arrow=2pt] (0,-1) to (2,-1);
\draw [line width=2pt,thick arrow=1,shorten >=5pt] (0,-2) to (2,-2);
\draw [line width=2pt,thick arrow=2,shorten >=5pt] (0,-3) to (2,-3);
\draw [thick arrow=2,shorten >=5pt,line width=2pt] (0,-3) to (2,-3);
\end{tikzpicture}
\end{document}

-
Nice. And for another suggestion: how about making it so that thick arrow=3 (ie with no explicit dimension) produces 3 times the current line width? –  Loop Space Aug 16 '11 at 12:03
Excellent suggestion. You'd be a good thesis supervisor. "Very interesting results. Now, why don't you look at what happens if you do <something that'll take another three months>?" =) –  Jake Aug 16 '11 at 12:31
In that spirit ... I notice that the arrow thickness uses the line width in effect when the option is declared; so thick arrow=2, line width=5pt is not the same as line width=5pt, thick arrow=2. If you put the code in the arrow declaration then it would be independent of the order. –  Loop Space Aug 16 '11 at 12:49
(Mind you ... I haven't actually tested that assertion; it's merely a conjecture.) –  Loop Space Aug 16 '11 at 12:50
Absolutely right! –  Jake Aug 16 '11 at 12:59
show 1 more comment