6

I know how to use let from TikZ calc library into a path command and fix some parameter of a particular node on that path. But I would like to use inside node's style definition.

As an example, consider how to automatically fix height and border rotation for a single arrow between to nodes:

\documentclass[tikz,border=2mm]{standalone} 
\usetikzlibrary{positioning, shapes.arrows, calc}

\begin{document}

\begin{tikzpicture}
\node[draw] (a) {A};
\node[draw, above right=2cm and 1cm of a] (b) {B};
\path (a.north east) let \p1=($(b.south west)-(a.north east)$) in
    node[single arrow, draw, 
        minimum height={veclen(\x1,\y1)}, 
        shape border uses incircle,
        shape border rotate={atan2(\y1,\x1)},
        anchor=tail] at (a.north east) {};
\end{tikzpicture}
\end{document}

enter image description here

I would like to define this arrow node with a style similar to arrow between a and b and be able to use

\node[arrow between a and b] {}; 

The problem is that I've never seen let inside a .\style for a node. Is it possible to do it?

Ignasi
  • 136,588

3 Answers3

3

It is what you are looking for ?

To answer the remark the node is not inside the node, as one can put text in it : enter image description here

\documentclass[tikz,border=2mm]{standalone} 
\usetikzlibrary{positioning, shapes.arrows, calc}

\begin{document}

\tikzset{/tikz/.cd,
    my arrow/.code args={#1 and #2}{%
        \path (#1.north east) let \p1=($(#2.south west)-(#1.north east)$) in
            node[single arrow, draw, 
            minimum height={veclen(\x1,\y1)}, 
%               shape border uses incircle,
%               shape border 
            rotate={atan2(\y1,\x1)},
            anchor=tail] at (#1.north east) {\MyAwTxt};
    },
    my arrow text/.store in=\MyAwTxt,
    my arrow text=,
}

\begin{tikzpicture}
\node[draw] (a) {A};
\node[draw, above right=2cm and 1cm of a] (b) {B};
\node[my arrow text=bob,my arrow={a and b}]{x};

\end{tikzpicture}
\end{document}

Was first try

enter image description here

\documentclass[tikz,border=2mm]{standalone} 
\usetikzlibrary{positioning, shapes.arrows, calc}

\begin{document}

\tikzset{/tikz/.cd,
    my arrow/.code args={#1 and #2}{%
        \path (#1.north east) let \p1=($(#2.south west)-(#1.north east)$) in
            node[single arrow, draw, 
            minimum height={veclen(\x1,\y1)}, 
            shape border uses incircle,
            shape border rotate={atan2(\y1,\x1)},
            anchor=tail] at (#1.north east) {};
    },
}

\begin{tikzpicture}
\node[draw] (a) {A};
\node[draw, above right=2cm and 1cm of a] (b) {B};

%\node[my arrow={a and b}] {}; % <- OP's request

\path[my arrow={a and b}] ; % <- My first idea

\end{tikzpicture}
\end{document}
Tarass
  • 16,912
  • But this way you insert a node inside another one, is this formally correct? – Ignasi Mar 13 '18 at 16:29
  • Certainly not ;-) See my edit. – Tarass Mar 13 '18 at 16:38
  • I don't thing that code args nor style args can be used with the syntax you want : arrow between a and b. – Tarass Mar 13 '18 at 17:17
  • If you're doing \path[my arrow]; you could just do insert path=(#1.north east) let … in node … {}. There's no need to start a \path inside another \path. Or you keep the definition of my arrow and just do \tikzset{my arrow=a and b}. – Qrrbrbirlbel Sep 06 '22 at 09:54
3

Probably I am missing something, but you could do something along the lines of this answer. UPDATE @Tarass told me that the text should be inside the arrow, which is trivially accomplishable. If I knew all the requirements, I'd be happy to implement them.

\documentclass[tikz,border=2mm]{standalone} 
\usetikzlibrary{positioning, shapes.arrows, calc}
\usetikzlibrary{calc}
\tikzset{
  my arrow/.style n args={2}{at={($(#1)!0.5!(#2)$)},
    append after command={
      \pgfextra{\path (#1.north east) let \p1=($(#2.south west)-(#1.north east)$) in
    node[single arrow, draw, 
        minimum height={veclen(\x1,\y1)}, 
        shape border uses incircle,
        shape border rotate={atan2(\y1,\x1)},
        anchor=tail] at (a.north east) {};
    }}
  }
}
\begin{document}

\begin{tikzpicture}
\node[draw] (a) {A};
\node[draw, above right=2cm and 1cm of a] (b) {B};
\node[my arrow={a}{b}]{C};

\end{tikzpicture}
\end{document}

enter image description here

  • @Tarass I thought the challenge was to put this into a node command. Does your answer do that? –  Mar 13 '18 at 17:17
  • Yes it works directly. As Ignasi made a remark a bout the legality of using "a node inside a node" I wrote back my first idea. But finally I don't think that the node is "inside" as both nodes are empty : {}. – Tarass Mar 13 '18 at 17:21
  • I made an edit using node syntax and filling both nodes. – Tarass Mar 13 '18 at 17:30
  • @Tarass I already upvoted your post before that... –  Mar 13 '18 at 17:33
  • So am I ;-) we are even ! – Tarass Mar 13 '18 at 17:37
  • @Tarass Fine. I didn't now that the position of the text is a requirement and it will be easy to slope it if that's required. –  Mar 13 '18 at 17:39
  • It didn't and slope doesn't work for me, I had to rotate it with the arrow commenting two no effect options. – Tarass Mar 13 '18 at 17:41
  • @Tarass Sorry, there is the usual language barrier. I have no idea what "it didn't" means, but it is not too important since Ignasi seems to look for something else. –  Mar 13 '18 at 17:54
  • Sorry. It wasn't a requirement. – Tarass Mar 13 '18 at 18:56
2
  1. Yes, with the math library and its /tikz/evaluate key (not to be confused with /pgf/foreach/evaluate):

    \tikzset{
      arrow between/.style args={#1 and #2}{
        evaluate={
          coordinate \diffPoint;
          \diffPoint=(#2.south west)-(#1.north east);},
        shape=single arrow, draw,
        at={(#1.north east)}, anchor=tail,
        shape border uses incircle,
        minimum height=veclen(\diffPoint)*1pt,
        shape border rotate={atan2(\diffPointy,\diffPointx)}}}
    
  2. Yes, by using the let … in parser but deactivating the switching-back to \path parsing.

    \makeatletter
    \tikzset{parse let/.code={\def\tikz@cc@stop@let in{}\tikz@let@command et #1in}}
    \makeatother
    \tikzset{
      arrow between'/.style args={#1 and #2}{
        parse let={\p{diffPoint}=($(#2.south west)-(#1.north east)$)},
        shape=single arrow, draw,
        at={(#1.north east)}, anchor=tail,
        shape border uses incircle,
        minimum height=veclen(\p{diffPoint})*1pt,
        shape border rotate={atan2(\y{diffPoint},\x{diffPoint})}}}
    
  3. Or just using the calculations on the PGF level

    • where \pgfkeyssetevalue{<key>}{<value>} is the faster version of \pgfkeys{<key>/.expanded=<value>},
    • where \pgfmathveclen@ is the faster version of \pgfmathveclen or \pgfmathparse{veclen(…) since the parameters don't need to get parsed again and
    • where we're re-using the globalized dimensions \pgf@x and \pgf@y from \pgfmathanglebetweenpoints' \pgfpointdiff for the veclen function:
    \tikzset{
      arrow between''/.style args={#1 and #2}{%
        shape=single arrow, draw,
        at={(#1.north east)}, anchor=tail,
        shape border uses incircle,
        /utils/exec=%
          \pgfmathanglebetweenpoints
            {\pgfpointanchor{#1}{north east}}{\pgfpointanchor{#2}{south west}}%
          \pgfkeyssetevalue{/pgf/shape border rotate}{\pgfmathresult}%
          \pgfmathveclen@{\pgf@x}{\pgf@y}%
          \pgfkeyssetevalue{/pgf/minimum height}{\pgfmathresult pt}}}
    

Of course, all solutions need some work to detect the needed anchors. (Is #2 lower or highter than #1? Right or left of? What anchors should be used when they're (almost) vertically or horizontally aligned?)

Code

\documentclass[tikz,border=2mm]{standalone} 
\usetikzlibrary{positioning, shapes.arrows, calc, math}
% 1. math library that allows 'calc'ulations:
\tikzset{
  arrow between/.style args={#1 and #2}{
    evaluate={
      coordinate \diffPoint;
      \diffPoint=(#2.south west)-(#1.north east);},
    shape=single arrow, draw,
    at={(#1.north east)}, anchor=tail,
    shape border uses incircle,
    minimum height=veclen(\diffPoint)*1pt,
    shape border rotate={atan2(\diffPointy,\diffPointx)}}}
% 2. using the original let syntax
\makeatletter
\tikzset{parse let/.code={\def\tikz@cc@stop@let in{}\tikz@let@command et #1in}}
\makeatother
\tikzset{
  arrow between'/.style args={#1 and #2}{
    parse let={\p{diffPoint}=($(#2.south west)-(#1.north east)$)},
    shape=single arrow, draw,
    at={(#1.north east)}, anchor=tail,
    shape border uses incircle,
    minimum height=veclen(\p{diffPoint})*1pt,
    shape border rotate={atan2(\y{diffPoint},\x{diffPoint})}}}
% 3. using lower level PGF commands
\makeatletter
\tikzset{
  arrow between''/.style args={#1 and #2}{%
    shape=single arrow, draw,
    at={(#1.north east)}, anchor=tail,
    shape border uses incircle,
    /utils/exec=%
      \pgfmathanglebetweenpoints
        {\pgfpointanchor{#1}{north east}}{\pgfpointanchor{#2}{south west}}%
      \pgfkeyssetevalue{/pgf/shape border rotate}{\pgfmathresult}%
      \pgfmathveclen@{\pgf@x}{\pgf@y}%
      \pgfkeyssetevalue{/pgf/minimum height}{\pgfmathresult pt}}}
\makeatother
\begin{document}
\begin{tikzpicture}
\node[draw] (a) {A};
\node[draw, above right=2cm and 1cm of a] (b) {B};
\node[arrow between=a and b] {};
\end{tikzpicture}

\begin{tikzpicture} \node[draw] (a) {A}; \node[draw, above right=2cm and 1cm of a] (b) {B}; \node[arrow between'=a and b] {}; \end{tikzpicture}

\begin{tikzpicture} \node[draw] (a) {A}; \node[draw, above right=2cm and 1cm of a] (b) {B}; \node[arrow between''=a and b] {}; \end{tikzpicture} \end{document}

Qrrbrbirlbel
  • 119,821
  • 1
    There's also a solution to use a to path, i.e. \draw(a)to[arrow between](b); where arrow between uses \tikztostart and \tikztotarget do all its calculations and instead of defining an actual path, just places the node following the same calculations. – Qrrbrbirlbel Sep 06 '22 at 00:01
  • Defining parse let/.style={insert path={let#1in}} will possibly work, too, but why start the full parser when all we need is let … in? – Qrrbrbirlbel Sep 06 '22 at 00:09