2

I'm making a network diagram with shaped arrows that go the whole length of the path, rather than just being a line with an arrow on the end.

Because the bend option is so easy to use, I chose to do this with arrows, rather than decorations.

The code below works for me. I could use it as-is, but there are a couple of things that seem kind of hackish.

1) Using a global length to store the original path length by using a preaction decoration seems like a hack, albeit somewhat clever. Is there a better (cleaner, less hackish) way of getting the original path length?

2) The bounding box for the arrow (as defined by the convex hull in the arrow definition) isn't right for the bent arrow. Is there any way to fix this? I tried using \pgfusepath{use as bounding box}, but that's not available in the drawing code (as specified in the manual).

image of arrow on curve

\documentclass[border=10pt,multi,tikz]{standalone}

\usetikzlibrary{arrows.meta}
\usetikzlibrary{bending}
\usetikzlibrary{decorations}

\newlength{\mypathlength}

\pgfdeclaredecoration{stashlength}{final}{
  \state{final}{
     \setlength{\mypathlength}{\pgfdecoratedpathlength}
     \global\mypathlength=\mypathlength}}


\pgfdeclarearrow{
  name = foo,
  parameters = {%
   \the\pgfarrowlength 
  },
  setup code = {%
    % The different end values:
    \pgfarrowssettipend{.25\pgfarrowlength}
    \pgfarrowssetlineend{-.25\pgfarrowlength}
    \pgfarrowssetvisualbackend{-.5\pgfarrowlength}
    \pgfarrowssetbackend{-.75\pgfarrowlength}
    % The hull
    \pgfarrowshullpoint{.25\pgfarrowlength}{0pt}%
    \pgfarrowsupperhullpoint{-.75\pgfarrowlength}{.5\pgfarrowlength}%
    \pgfarrowsupperhullpoint{-\mypathlength}{-.5\pgfarrowlength}% doesn't create the right bounding box
    % Saves: Only the length:
    \pgfarrowssavethe\pgfarrowlength
  },
  drawing code = {%
    \pgfpathmoveto{\pgfqpoint{.25\pgfarrowlength}{0pt}}
    \pgfpathlineto{\pgfpoint{-\mypathlength-.25\pgfarrowlength}{.5\pgfarrowlength}}
    \pgfpathlineto{\pgfpoint{-\mypathlength}{0pt}}
    \pgfpathlineto{\pgfpoint{-\mypathlength-.25\pgfarrowlength}{-.5\pgfarrowlength}}
    \pgfpathclose
    %\pgfusepath{use as bounding box}  %won't work as described in the manual (page 1019) -- would try to add arrows recursively
    \pgfusepathqstroke
  },
  defaults = {%
   length = 4cm 
  },
}

\begin{document} 
\begin{tikzpicture}
  %comment the next line out to see bounding box problems
  \node at (0,2) {Used to save space for arrow};
  \draw [-{foo[bend]},preaction={decorate,decoration=stashlength}] (0,0) to [bend right] (8,0);

  %\draw[-{foo[length=15pt]},preaction={decorate,decoration=stashlength}] (0,0) -- (8,0);

\end{tikzpicture}
\end{document}
1
  • It is always helpful to give a page/section reference for a manual as long as this one!
    – cfr
    Feb 23, 2017 at 22:59

1 Answer 1

4

EDIT I realised that the setup code still didn't match the drawing code. I've tried to guess what you want and sync them, but the crucial thing is that the points specified for the hull in the setup match those actually used to construct the tip when drawing.

You can improve the results by setting the hull points correctly. In your current code, they do not correspond to what you go onto draw. Hence, they appear not to affect the saved bounding box.

In particular, I believe that you need

\pgfarrowsupperhullpoint{-\mypathlength\advance\pgf@x by-.75\pgfarrowlength}{.5\pgfarrowlength}% it saves the bounding box correctly if it corresponds to the points actually used in the drawing code

as this appears to be what you actually want to draw. This solves a bounding box problem which was independent of your use of bend and affected the use of foo on even a straight path.

without fix

Note that the tail of the arrow is not captured within the dashed rectangle representing the bounding box.

With the change, the complete arrow is included in the dashed bounding box.

with fix

This does not completely solve the problem, however, because it does not account for the maximum and minimum values of x and y on the path tracing the arrow tip, but only their values at the specified points. This is fine when the arrow is not bent because the line is straight, but it will fail in some cases where bend is used. This includes the case involving the arrow in your example. The tail of the arrow is now completely enclosed by the bounding box, as expected. But the belly of the arrow is not.

failure even with fixed code

However, this also happens with the usual arrow tips:

failure with standard tip

Note that you don't really need a new length. \pgfarrowlength should be the length of the arrow and \pgfarrowwidth can be used for the width.

Complete code for partial improvement:

\documentclass[border=20pt,tikz]{standalone}
\usetikzlibrary{arrows.meta,bending,decorations}

\pgfdeclaredecoration{stashlength}{final}{
  \state{final}{
    \setlength{\pgfarrowlength}{\pgfdecoratedpathlength}%
    \global\pgfarrowlength=\pgfarrowlength}}

\makeatletter
\pgfdeclarearrow{
  name = foo,
  parameters = {%
   \the\pgfarrowlength,
   \the\pgfarrowwidth,
  },
  setup code = {%
    % The different end values:
    \pgfarrowssettipend{.25\pgfarrowwidth}
    \pgfarrowssetlineend{-.25\pgfarrowwidth}
    \pgfarrowssetvisualbackend{-\pgfarrowlength\advance\pgf@x by-.5\pgfarrowwidth}
    \pgfarrowssetbackend{-\pgfarrowlength\advance\pgf@x by-.75\pgfarrowwidth}
    % The hull
    \pgfarrowshullpoint{-\pgfarrowlength\advance\pgf@x by-.5\pgfarrowwidth}{0pt}%
    \pgfarrowsupperhullpoint{-\pgfarrowlength\advance\pgf@x by-.75\pgfarrowwidth}{.5\pgfarrowwidth}% it saves the bounding box correctly if it corresponds to the points actually used in the drawing code
    % Saves: Only the length:
    \pgfarrowssavethe\pgfarrowwidth
    \pgfarrowssavethe\pgfarrowlength
  },
  drawing code = {%
    \pgfpathmoveto{\pgfqpoint{.25\pgfarrowwidth}{0pt}}
    \pgfpathlineto{\pgfpoint{-\pgfarrowlength-.75\pgfarrowwidth}{.5\pgfarrowwidth}}
    \pgfpathlineto{\pgfpoint{-\pgfarrowlength-.5\pgfarrowwidth}{0pt}}
    \pgfpathlineto{\pgfpoint{-\pgfarrowlength-.75\pgfarrowwidth}{-.5\pgfarrowwidth}}
    \pgfpathclose
    \pgfusepathqstroke
  },
  defaults = {%
   width = 4cm
  },
}
\makeatother
\newcommand*\drawbb{\draw [densely dashed, gray] (current bounding box.south east) rectangle (current bounding box.north west);}
\begin{document}
\begin{tikzpicture}
  %comment the next line out to see bounding box problems
%   \node at (0,2) {Used to save space for arrow};
  \draw [-{foo[bend]},preaction={decorate,decoration=stashlength}] (0,0) to [bend right] (8,0);
  %\draw[-{foo[length=15pt]},preaction={decorate,decoration=stashlength}] (0,0) -- (8,0);
  \drawbb
\end{tikzpicture}
\begin{tikzpicture}
  \draw[-{foo[width=15pt]},preaction={decorate,decoration=stashlength}, postaction={draw=red, -}] (0,0) -- (1,0);
  \drawbb
  \draw [help lines] (-1,-1) grid (6,1);
\end{tikzpicture}
\begin{tikzpicture}
  %comment the next line out to see bounding box problems
%   \node at (0,2) {Used to save space for arrow};
  \draw [-{foo[bend]},preaction={decorate,decoration=stashlength}] (0,0) to [bend right] (20,0);
  %\draw[-{foo[length=15pt]},preaction={decorate,decoration=stashlength}] (0,0) -- (8,0);
  \drawbb
\end{tikzpicture}
\begin{tikzpicture}
  \draw [-{Stealth[bend, length=10cm]}] (0,0) to [bend right] (8,0);
  \drawbb
\end{tikzpicture}
\end{document}
8
  • I see how this is better, and fixes a mistake I have made. Will the setup code be executed every time an arrow is drawn? I thought I read the manual to say it wouldn't, so I think that the bounding box won't change if I change the length of the path. Of course, some experiments I am working on now indicate that in contrast to what I expected from reading the manual, the arrow length also doesn't change if I change the length of the path... Feb 24, 2017 at 0:19
  • @CarlSorensen Please see also edit above. The arrow depends on the length and the width, so these need to both be given as parameters to ensure that the setup code is reexecuted when necessary. It will be executed once for each unique set of parameters. There has to be a better way of setting the length, though. I agree. Wish I knew what it is!
    – cfr
    Feb 24, 2017 at 0:35
  • @CarlSorensen Please see further edits above. Since the remaining failure occurs with standard tips, too, I suspect you may be stuck with it unless you want to make your arrow tip do non-arrowy-tip things, assuming that's possible.
    – cfr
    Feb 24, 2017 at 2:35
  • I do want my arrow tip to do non-arrowy-tip things. I want to replace the path with an arrow tip. I don't want to set the length, because I don't know what the length of the path will be. I get the length from the decoration call. Feb 24, 2017 at 3:01
  • @CarlSorensen My code only sets length explicitly` for the example with Stealth. It gets it from the decoration for foo. So I don't understand your comment about not wanting to set length. I'm setting it just the way you did. What I mean about non-arrowy-tip things, is that I think there is going to be a limit to what you can expect to do with a framework designed for something very specific. If you can deal with the limitations in the Stealth case, that's one thing. But if you need bounding-box magic beyond the standard, good luck. Lots of things are deliberately disabled for ...
    – cfr
    Feb 24, 2017 at 3:06

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.