3

For my mindmap, I would like to replace the "volcano" at the end of the connection with an arrow.

Since the connection is supposed to be curved, I use the approach from this answer. I have tried a lot but my knowledge in tikz/pgf is unfortunately not sufficient to get the arrow drawn correctly. I thought the arrow was best defined where the definition of the second volcano originally was. At this point I have left an associated comment in the code.

Here is what I have so far:

\documentclass[tikz,border=0pt]{standalone}
\usetikzlibrary{positioning, mindmap}

\makeatletter
\newdimen\pgfutil@tempdimc

\pgfdeclaredecoration{circle connection boomerang}{initial}
{
  \state{initial}[width=0pt,next state=boomerang]
  {
    {
    \pgftransformxshift{-\pgfkeysvalueof{/pgf/decoration/start radius}}%
    \pgftransformrotate{\pgfdecorationsegmentaspect}%%%
    \pgfpathmoveto{\pgfpointpolar{\pgfdecorationsegmentangle}{\pgfkeysvalueof{/pgf/decoration/start radius}}}
    \pgfpatharc{\pgfdecorationsegmentangle}{-\pgfdecorationsegmentangle}{\pgfkeysvalueof{/pgf/decoration/start radius}}
    \pgfutil@tempcnta=-\pgfdecorationsegmentangle\relax
    \advance\pgfutil@tempcnta by90\relax
    \pgfmathsetlength\pgfutil@tempdima{\pgfkeysvalueof{/pgf/decoration/start radius}}
    \pgfmathsetlength\pgfutil@tempdimb{\pgfdecorationsegmentamplitude}
    \pgfpathcurveto
    {\pgfpointadd
      {\pgfpointpolar{-\pgfdecorationsegmentangle}{\pgfkeysvalueof{/pgf/decoration/start radius}}}
      {\pgfpointpolar{\the\pgfutil@tempcnta}{.25\pgfutil@tempdima}}}
    {\pgfqpoint{1.25\pgfutil@tempdima}{-.5\pgfutil@tempdimb}}
    {\pgfqpoint{1.5\pgfutil@tempdima}{-.5\pgfutil@tempdimb}}
    \pgfpathlineto{\pgfpoint{1.5\pgfutil@tempdima}{.5\pgfutil@tempdimb}}
    \pgfutil@tempcnta=\pgfdecorationsegmentangle\relax
    \advance\pgfutil@tempcnta by-90\relax
    \pgfpathcurveto
    {\pgfpoint{1.25\pgfutil@tempdima}{.5\pgfutil@tempdimb}}
    {\pgfpointadd
      {\pgfpointpolar{\pgfdecorationsegmentangle}{\pgfkeysvalueof{/pgf/decoration/start radius}}}
      {\pgfpointpolar{\the\pgfutil@tempcnta}{.25\pgfutil@tempdima}}}
    {\pgfpointpolar{\pgfdecorationsegmentangle}{\pgfkeysvalueof{/pgf/decoration/start radius}}}
    \pgfpathclose
    }
  }
  \state{boomerang}[width=0pt,next state=end]
  {
    \pgfmathsetlength\pgfutil@tempdima{\pgfkeysvalueof{/pgf/decoration/start radius}}%
    \pgfmathsetlength\pgfutil@tempdimb{\pgfkeysvalueof{/pgf/decoration/end radius}}%
    \pgfmathsetlength\pgfutil@tempdimc{\pgfdecorationsegmentamplitude}%
    \pgftransformxshift{-\pgfutil@tempdima}
    \pgfpathmoveto
    {\pgfpointadd
      {\pgfpointpolar{\pgfdecorationsegmentaspect}{1.5\pgfutil@tempdima}}
      {\pgfpointpolar{\pgfdecorationsegmentaspect-90}{.5\pgfutil@tempdimc}}}
    \pgftransformxshift{\pgfdecoratedremainingdistance+\pgfutil@tempdima+\pgfutil@tempdimb}
    \pgfpathquadraticcurveto
    {\pgfpoint{-.5\pgfdecoratedremainingdistance-\pgfutil@tempdimb}{(.5\pgfdecoratedremainingdistance+.5\pgfutil@tempdima+.5\pgfutil@tempdimb)*tan(\pgfdecorationsegmentaspect)-.5\pgfutil@tempdimc}}
     {\pgfpointadd
      {\pgfpointpolar{-\pgfdecorationsegmentaspect}{-1.5\pgfutil@tempdimb}}
      {\pgfpointpolar{-\pgfdecorationsegmentaspect-90}{.5\pgfutil@tempdimc}}}
    \pgfpathlineto{\pgfpointadd
      {\pgfpointpolar{-\pgfdecorationsegmentaspect}{-1.5\pgfutil@tempdimb}}
      {\pgfpointpolar{-\pgfdecorationsegmentaspect+90}{.5\pgfutil@tempdimc}}}
    \pgftransformxshift{-\pgfdecoratedremainingdistance-\pgfutil@tempdima-\pgfutil@tempdimb}
    \pgfpathquadraticcurveto
    {\pgfpoint{.5\pgfdecoratedremainingdistance+\pgfutil@tempdima}{(.5\pgfdecoratedremainingdistance+.5\pgfutil@tempdima+.5\pgfutil@tempdimb)*tan(\pgfdecorationsegmentaspect)+.5\pgfutil@tempdimc}}
    {\pgfpointadd
      {\pgfpointpolar{\pgfdecorationsegmentaspect}{1.5\pgfutil@tempdima}}
      {\pgfpointpolar{\pgfdecorationsegmentaspect+90}{.5\pgfutil@tempdimc}}}
    \pgfpathclose
  }
  \state{end}[width=0pt,next state=final]
  {
    {
    % the arrow should be defined here
    %
    % original code producing the "volcano"
    % \pgftransformxshift{\pgfdecoratedremainingdistance}%
    % \pgftransformxscale{-1}%
    % \pgftransformxshift{-\pgfkeysvalueof{/pgf/decoration/end radius}}%
    % \pgftransformrotate{\pgfdecorationsegmentaspect}%%%
    % \pgfpathmoveto{\pgfpointpolar{\pgfdecorationsegmentangle}{\pgfkeysvalueof{/pgf/decoration/end radius}}}
    % \pgfpatharc{\pgfdecorationsegmentangle}{-\pgfdecorationsegmentangle}{\pgfkeysvalueof{/pgf/decoration/end radius}}
    % \pgfutil@tempcnta=-\pgfdecorationsegmentangle\relax
    % \advance\pgfutil@tempcnta by90\relax
    % \pgfmathsetlength\pgfutil@tempdima{\pgfkeysvalueof{/pgf/decoration/end radius}}
    % \pgfmathsetlength\pgfutil@tempdimb{\pgfdecorationsegmentamplitude}%
    % \pgfpathcurveto
    % {\pgfpointadd
    %   {\pgfpointpolar{-\pgfdecorationsegmentangle}{\pgfkeysvalueof{/pgf/decoration/end radius}}}
    %   {\pgfpointpolar{\the\pgfutil@tempcnta}{.25\pgfutil@tempdima}}}
    % {\pgfqpoint{1.25\pgfutil@tempdima}{-.5\pgfutil@tempdimb}}
    % {\pgfqpoint{1.5\pgfutil@tempdima}{-.5\pgfutil@tempdimb}}
    % \pgfpathlineto{\pgfpoint{1.5\pgfutil@tempdima}{.5\pgfutil@tempdimb}}
    % \pgfutil@tempcnta=\pgfdecorationsegmentangle\relax
    % \advance\pgfutil@tempcnta by-90\relax
    % \pgfpathcurveto
    % {\pgfpoint{1.25\pgfutil@tempdima}{.5\pgfutil@tempdimb}}
    % {\pgfpointadd
    %   {\pgfpointpolar{\pgfdecorationsegmentangle}{\pgfkeysvalueof{/pgf/decoration/end radius}}}
    %   {\pgfpointpolar{\the\pgfutil@tempcnta}{.25\pgfutil@tempdima}}}
    % {\pgfpointpolar{\pgfdecorationsegmentangle}{\pgfkeysvalueof{/pgf/decoration/end radius}}}
    % \pgfpathclose
    }
  }
  \state{final}
  {}
}
\tikzoption{circle connection boomerang switch color}{\tikz@parse@switch@boomerang#1\pgf@unique}
\def\tikz@parse@switch@boomerang from (#1) to (#2) by #3\pgf@unique{%
  \tikzset{to path={%
      \pgfextra{%
        \tikz@lib@mindmap@check{\tikztostart}%
        \tikz@compute@circle@radii\tikz@compute@segmentamplitude%
        \global\let\tikz@lib@save@start=\tikztostart%
        \global\let\tikz@lib@save@target=\tikztotarget%
        \pgfkeysgetvalue{/pgf/decoration/start radius}\pgf@temp%
        \global\let\tikz@lib@saved@start@radius=\pgf@temp%
        \pgfkeysgetvalue{/pgf/decoration/end radius}\pgf@temp%
        \global\let\tikz@lib@saved@end@radius=\pgf@temp%
      }%
      [every circle connection bar]
      decorate [decoration={circle connection boomerang,aspect=#3}]
      { -- (\tikztotarget) \tikztonodes} 
    },
    append after command={
      [fill=none,draw=none,path picture=\tikz@lib@shade@pic]
      \pgfextra{
        \pgfutil@colorlet{tikz@switch@from}{#1}%
        \pgfutil@colorlet{tikz@switch@to}{#2}%
      }
    }
  }
}

\pgfdeclarehorizontalshading[tikz@switch@from,tikz@switch@to]{tikz@shade@bar}{10000bp}{%
  color(0pt)=(tikz@switch@from);
  color(100bp)=(tikz@switch@to)}

\tikzset{%
  concept color and bend/.code 2 args=
  {
    \let\tikz@old@concept@color=\tikz@concept@color%
    \def\tikz@edge@to@parent@path{
      (\tikzparentnode)
      to[circle connection boomerang switch color=from (\tikz@old@concept@color) to (#1) by #2]
      (\tikzchildnode)}
    \def\tikz@concept@color{#1}%
  }
}
   
\begin{document}
\begin{tikzpicture}
  \node[draw, circle, red] (n) {$n$};
  \node[draw, circle] (m) [below = of n] {$m$};
  \draw (n) to[circle connection boomerang switch color=from (red) to (black) by -5] (m);
\end{tikzpicture}
\end{document}

Producing

enter image description here

And this is how the result should look like:

enter image description here

6
  • 2
    Your code is not compilable. A}seem to be missing !
    – AndréC
    Nov 22, 2021 at 17:40
  • See tex.stackexchange.com/q/413793 for additional inspiration.
    – user255043
    Nov 22, 2021 at 17:45
  • @AndréC You are right, I have modified the sample code. Now it should compile.
    – Nico
    Nov 22, 2021 at 17:52
  • @ABC Thanks, I have looked at that as well, however I prefer the arrowhead at the end of the connection and the connection should be curved as well.
    – Nico
    Nov 22, 2021 at 17:54
  • 1
    Please make your code compilable Nov 22, 2021 at 22:29

1 Answer 1

3

I probably overlooked something, but this seems to look quite right (tested for different angles):

\documentclass[tikz,border=0pt]{standalone}
\usetikzlibrary{positioning, mindmap}

\makeatletter
\newdimen\pgfutil@tempdimc

\pgfdeclaredecoration{circle connection boomerang}{initial}
{
  \state{initial}[width=0pt,next state=boomerang]
  {
    {
    \pgftransformxshift{-\pgfkeysvalueof{/pgf/decoration/start radius}}%
    \pgftransformrotate{\pgfdecorationsegmentaspect}%%%
    \pgfpathmoveto{\pgfpointpolar{\pgfdecorationsegmentangle}{\pgfkeysvalueof{/pgf/decoration/start radius}}}
    \pgfpatharc{\pgfdecorationsegmentangle}{-\pgfdecorationsegmentangle}{\pgfkeysvalueof{/pgf/decoration/start radius}}
    \pgfutil@tempcnta=-\pgfdecorationsegmentangle\relax
    \advance\pgfutil@tempcnta by90\relax
    \pgfmathsetlength\pgfutil@tempdima{\pgfkeysvalueof{/pgf/decoration/start radius}}
    \pgfmathsetlength\pgfutil@tempdimb{\pgfdecorationsegmentamplitude}
    \pgfpathcurveto
    {\pgfpointadd
      {\pgfpointpolar{-\pgfdecorationsegmentangle}{\pgfkeysvalueof{/pgf/decoration/start radius}}}
      {\pgfpointpolar{\the\pgfutil@tempcnta}{.25\pgfutil@tempdima}}}
    {\pgfqpoint{1.25\pgfutil@tempdima}{-.5\pgfutil@tempdimb}}
    {\pgfqpoint{1.5\pgfutil@tempdima}{-.5\pgfutil@tempdimb}}
    \pgfpathlineto{\pgfpoint{1.5\pgfutil@tempdima}{.5\pgfutil@tempdimb}}
    \pgfutil@tempcnta=\pgfdecorationsegmentangle\relax
    \advance\pgfutil@tempcnta by-90\relax
    \pgfpathcurveto
    {\pgfpoint{1.25\pgfutil@tempdima}{.5\pgfutil@tempdimb}}
    {\pgfpointadd
      {\pgfpointpolar{\pgfdecorationsegmentangle}{\pgfkeysvalueof{/pgf/decoration/start radius}}}
      {\pgfpointpolar{\the\pgfutil@tempcnta}{.25\pgfutil@tempdima}}}
    {\pgfpointpolar{\pgfdecorationsegmentangle}{\pgfkeysvalueof{/pgf/decoration/start radius}}}
    \pgfpathclose
    }
  }
  \state{boomerang}[width=0pt,next state=end]
  {
    \pgfmathsetlength\pgfutil@tempdima{\pgfkeysvalueof{/pgf/decoration/start radius}}%
    \pgfmathsetlength\pgfutil@tempdimb{10pt}%                   % <-- changeable
    \pgfmathsetlength\pgfutil@tempdimc{\pgfdecorationsegmentamplitude}%
    \pgftransformxshift{-\pgfutil@tempdima}
    \pgfpathmoveto
    {\pgfpointadd
      {\pgfpointpolar{\pgfdecorationsegmentaspect}{1.5\pgfutil@tempdima}}
      {\pgfpointpolar{\pgfdecorationsegmentaspect-90}{.5\pgfutil@tempdimc}}}
    \pgftransformxshift{\pgfdecoratedremainingdistance+\pgfutil@tempdima+\pgfutil@tempdimb}
    \pgfpathquadraticcurveto
    {\pgfpoint{-.5\pgfdecoratedremainingdistance-\pgfutil@tempdimb}{(.5\pgfdecoratedremainingdistance+.5\pgfutil@tempdima+.5\pgfutil@tempdimb)*tan(\pgfdecorationsegmentaspect)-.5\pgfutil@tempdimc}}
     {\pgfpointadd
      {\pgfpointpolar{-\pgfdecorationsegmentaspect}{-1.5\pgfutil@tempdimb}}
      {\pgfpointpolar{-\pgfdecorationsegmentaspect-90}{.5\pgfutil@tempdimc}}}
    \pgfpathlineto{\pgfpointadd
      {\pgfpointpolar{-\pgfdecorationsegmentaspect}{-1.5\pgfutil@tempdimb}}
      {\pgfpointpolar{-\pgfdecorationsegmentaspect+90}{.5\pgfutil@tempdimc}}}
    \pgftransformxshift{-\pgfdecoratedremainingdistance-\pgfutil@tempdima-\pgfutil@tempdimb}
    \pgfpathquadraticcurveto
    {\pgfpoint{.5\pgfdecoratedremainingdistance+\pgfutil@tempdima}{(.5\pgfdecoratedremainingdistance+.5\pgfutil@tempdima+.5\pgfutil@tempdimb)*tan(\pgfdecorationsegmentaspect)+.5\pgfutil@tempdimc}}
    {\pgfpointadd
      {\pgfpointpolar{\pgfdecorationsegmentaspect}{1.5\pgfutil@tempdima}}
      {\pgfpointpolar{\pgfdecorationsegmentaspect+90}{.5\pgfutil@tempdimc}}}
    \pgfpathclose
  }
  \state{end}[width=0pt,next state=final]
  {
    {
    \pgftransformxshift{\pgfdecoratedremainingdistance}%
    \pgftransformxscale{-1}
    \pgfmathsetlength\pgfutil@tempdima{10pt}%
    \pgftransformxshift{-\pgfutil@tempdima}%
    \pgfpathmoveto{\pgfpointpolar{\pgfdecorationsegmentaspect}{\pgfutil@tempdima}}
    \pgfpathlineto{\pgfpointadd
        {\pgfpointpolar{\pgfdecorationsegmentaspect-90}{-2pt}}  % <-- changeable
        {\pgfpointpolar{\pgfdecorationsegmentaspect}{1.5*\pgfutil@tempdima}}}
    \pgfpathlineto{\pgfpointadd
        {\pgfpointpolar{\pgfdecorationsegmentaspect-90}{2pt}}   % <-- changeable
        {\pgfpointpolar{\pgfdecorationsegmentaspect}{1.5*\pgfutil@tempdima}}}
    \pgfpathclose
    }
  }
  \state{final}
  {}
}
\tikzoption{circle connection boomerang switch color}{\tikz@parse@switch@boomerang#1\pgf@unique}
\def\tikz@parse@switch@boomerang from (#1) to (#2) by #3\pgf@unique{%
  \tikzset{to path={%
      \pgfextra{%
        \tikz@lib@mindmap@check{\tikztostart}%
        \tikz@compute@circle@radii\tikz@compute@segmentamplitude%
        \global\let\tikz@lib@save@start=\tikztostart%
        \global\let\tikz@lib@save@target=\tikztotarget%
        \pgfkeysgetvalue{/pgf/decoration/start radius}\pgf@temp%
        \global\let\tikz@lib@saved@start@radius=\pgf@temp%
        \pgfkeysgetvalue{/pgf/decoration/end radius}\pgf@temp%
        \global\let\tikz@lib@saved@end@radius=\pgf@temp%
      }%
      [every circle connection bar]
      decorate [decoration={circle connection boomerang,aspect=#3}]
      { -- (\tikztotarget) \tikztonodes} 
    },
    append after command={
      [fill=none,draw=none,path picture=\tikz@lib@shade@pic]
      \pgfextra{
        \pgfutil@colorlet{tikz@switch@from}{#1}%
        \pgfutil@colorlet{tikz@switch@to}{#2}%
      }
    }
  }
}

\pgfdeclarehorizontalshading[tikz@switch@from,tikz@switch@to]{tikz@shade@bar}{10000bp}{%
  color(0pt)=(tikz@switch@from);
  color(100bp)=(tikz@switch@to)}

\tikzset{%
  concept color and bend/.code 2 args=
  {
    \let\tikz@old@concept@color=\tikz@concept@color%
    \def\tikz@edge@to@parent@path{
      (\tikzparentnode)
      to[circle connection boomerang switch color=from (\tikz@old@concept@color) to (#1) by #2]
      (\tikzchildnode)}
    \def\tikz@concept@color{#1}%
  }
}
   
\begin{document}
\begin{tikzpicture}
  \node[draw, circle, red] (n) {$n$};
  \node[draw, circle] (m) [below = of n] {$m$};
  \draw (n) to[circle connection boomerang switch color=from (red) to (black) by 20] (m);
\end{tikzpicture}
\end{document}

enter image description here

Note that I needed to change one line in the boomerang state (the line where the first changeable annotation stands) to make the arrow fixed size, regardless of the size of the node it points to.

As for now, the size of the arrow is hard coded in the style, which is probably not optimal. You can adapt the size and width of the arrow by changing the values 10pt, -2pt and 2pt in the lines marked with the changeable annotations.

Note also that there seems to be a bug in the original code from the answer on which this code bases: If you set the angle to a higher value, such as 50, the gradient will not fill the whole arrow anymore and some parts will become transparent and therefore invisible. Sadly, I haven't found a solution to this problem.

5
  • First of all, thank you very much, this already looks very good. However, two things are still not quite ideal, especially for the transfer to my actual problem. You have already addressed the first point: Could you think of a way to adapt the size of the arrow or even take the arrowhead from the tikzlibraries arrows or arrows.meta? And second: If I set the angle to a higher value (e.g. 50) both the beginning and the end of the arrow are not displayed correctly anymore and are overlaid with a white rectangle.
    – Nico
    Nov 23, 2021 at 15:49
  • You are right, something's clipping the arrow head off. But, guess what, this is also the case for the code that we use as basis. Try to put 50 as angle and you see the very same effect. I guess this is because the gradient is not large enough to cover the whole curved line or something like that. Nov 23, 2021 at 18:35
  • As for the arrows: I don't know how to place an arrowhead here, but you can adapt the size of the arrow by changing the last values in the three lines marked by changeable in the code. Nov 23, 2021 at 18:48
  • @JasperHabicht what a classy answer !
    – JeT
    Nov 23, 2021 at 21:39
  • @JasperHabicht Yes I noticed this in the original code as well. I'll accept your answer as it answers the question posed. Thanks again.
    – Nico
    Nov 24, 2021 at 8:44

You must log in to answer this question.

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