2

I explored multiple solution to represent a graph cycle in TikZ with arbitrary nodes distributed around a circle and joined by arrows like the example below.

enter image description here

What worked for me was to place nodes on a chain using polar coordinates and then call a macro I wrote (\arcarrow) to draw an joining nodes. \arcarrow takes 4 arguments : an optional style, two nodes, and the center of the arc. It uses the intersections library to compute pseudo-anchor coordinates for the arrows.

Here is the MWE :

\documentclass[
]{article}

\usepackage{tikz}
\usetikzlibrary{intersections, chains, scopes, through, calc, shapes.geometric}


% \arcarrow
% draw an arc bewteen two nodes with a given center
% TODO : find a way to intersect with nodes without prior setting of a "name path global"
\newcommand\arcarrow[4][solid]{
 % #1 style; optional, default = solid
 % #2 start node
 % #3 end node
 % #4 center
 
 % don't really need a node here but "circle through" does
 \node [draw = none, name path = hcirc] at (#4) [circle through=(#2)] {};
 
 % find intersection of nodes with help circle
 \foreach \n in {#2,#3}
  \path [name intersections={of = hcirc and \n,sort by = hcirc}]
   (intersection-1) coordinate (\n-1)
   %[fill=red, opacity=0.5] circle (2pt) [above left] node {\n-1} % display point
   (intersection-2) coordinate (\n-2)
   %[fill=red, opacity=0.5] circle (2pt) [above left] node {\n-2} % display point
   ;
 
 %calculate polar angles
 \pgfmathanglebetweenpoints{\pgfpointanchor{#4}{center}}{%
  \pgfpointanchor{#2-1}{center}} 
 \let\aan\pgfmathresult
  
 \pgfmathanglebetweenpoints{\pgfpointanchor{#4}{center}}{%
  \pgfpointanchor{#2-2}{center}} 
 \let\ban\pgfmathresult
 
 \pgfmathanglebetweenpoints{\pgfpointanchor{#4}{center}}{%
  \pgfpointanchor{#3-1}{center}} 
 \let\can\pgfmathresult
 
 \pgfmathanglebetweenpoints{\pgfpointanchor{#4}{center}}{%
  \pgfpointanchor{#3-2}{center}} 
 \let\dan\pgfmathresult 
 
 % Find the starting point and the angles
 % This is tricky when the node center is around O°
 
 \pgfmathparse{(\ban-\aan)<180 ? 1 : 2}
 \edef\startpoint{#2-\pgfmathresult}
 \pgfmathparse{(\ban-\aan)<180 ? \aan : \ban}
 \let\startan\pgfmathresult
 \pgfmathparse{(\dan-\can)<180 ? \dan : \can}
 \let\endan\pgfmathresult
 
 % Draw the arrow
 % we have to compute radius with \p1 and \n1 (see https://tikz.dev/tutorial-Euclid#autosec-263)
 \draw [#1] (\startpoint) let 
                            \p1 = ($ (#2) - (#4) $),
                            \n1 = {veclen(\x1,\y1)}
                          in
                            arc (\startan:\endan:\n1);
}

\begin{document}

\begin{figure}\centering\begin{tikzpicture}[node distance=2cm]

 \tikzstyle{mynode} = [align=center, draw=black]
 \tikzstyle{arrow} = [thick,->,>=latex]
 
 \def \orig {90} % so we can change the origin without other things
 \def \radius {4cm}
 \coordinate (I) at (0,0); %TODO:use this center for polar coordinate so we can translate the whole diagram

 { [start chain=loop placed {at=(\orig-\tikzchaincount*90+90:\radius)}]
  % draw nodes on chain
  \node foreach \s in {circle, isosceles triangle, rectangle, trapezium}
   [mynode, \s, on chain, name path global = loop-\tikzchaincount] {\s};
  % draw arrows between nodes
  % TODO : find a more TikZ and less LaTeX way to do this (i.e. redefine a chain join) if possible
  \arcarrow [arrow] {loop-1} {loop-2} {I}
  \arcarrow [arrow] {loop-2} {loop-3} {I}
  \arcarrow [arrow] {loop-3} {loop-4} {I}
  \arcarrow [arrow, dashed] {loop-4} {loop-1} {I}
 }
 
\end{tikzpicture}\caption{The most robust and pretty cycle created by Ti\textit{k}Z i.e. \LaTeX, at the expense of using intersections package and doing angles calculation.}\end{figure}


\end{document}

I am quite happy with it except for two things that bugs me.

  1. Since the intersections library works with paths, not nodes, I must name the nodes paths in a way I can retrieve them from within the macro definition.
  2. Even worst it has to be "global" (name path global).

Though I am looking for another way to access the nodes' paths from within the \arcarrow macro.

The best solution would be to be able to set name path on the existing nodes passed to the macro. Is it possible ?

The second solution would be to assign an arbitrary name path, preferably not global, and preferably automaticaly (maybe with some \pgfkeys and some .style/.code stuff). Plus there would be a way to access this name path from within the macro.

I don't know the underlying of TikZ so it may not makes sense and I may be stuck with my name path global.

4
  • 1
    I think, we had similar questions here before. For a dirty solution: draw a full circle first and then place the nodes with fill=white. Your solution also ignores the line width. The angular border will lie on the outside of the line. The intersection will be calculated with the middle of the line. Though, this will probably be lost to TikZ imprecisions anyway as long as you use sensible line widths. Jul 31 at 18:58
  • Related: tex.stackexchange.com/q/103663 Jul 31 at 19:03
  • As pointed out by @Qrrbrbirlbel, this doesn't work with large line width. It should be possible to make an accurate calculation, but an angle "margin" might be a more practical option.
    – ChrisAga
    Aug 1 at 9:51
  • I just guess that you made some complexification of your own, as I did before in here tex.stackexchange.com/questions/511183/…
    – Black Mild
    Aug 2 at 6:12

2 Answers 2

2

I cannot answer the main question, i.e. I do not know a way to avoid name path, nor name path global. However, these keys are probably not really dangerous. All they do is to create a, possibly global, macro that contains the path. The macro name is designed not to interfere with other macros. Note that simply defining (named) nodes does the same, i.e. create global macros with unusual names.

What this answer offers is to do things in a more tikz way. First of all, you can avoid invoking name path global explicitly, you can make this part of the mynode style, it just gives the path a name that is derived from the node name. Of course you need to name the nodes to make this work. Second, you can use pics instead of the \arcarrow macro. These pics can have an argument that makes it easier to understand the usage. This usage is pic[options]{arc arrow=from <from> to <target> with center at <center>}. The output is as in your code. (I did not change the way things are computed/constructed.)

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{intersections, chains, scopes, through, calc, shapes.geometric,
    arrows.meta,bending}

\tikzset{pics/arc arrow/.style args={from #1 to #2 with center at #3}{code={%
 % style is obsolete now because it can be given to the pic in the usual way
 % #1 start node
 % #2 end node
 % #3 center
 % don't really need a node here but "circle through" does
 \node [draw = none, name path = hcirc] at (#3) [circle through=(#1)] {};
 
 % find intersection of nodes with help circle
 \foreach \n in {#1,#2}
  \path [name intersections={of = hcirc and aux-path-\n,sort by = hcirc}]
   (intersection-1) coordinate (aux-\n-1)
   %[fill=red, opacity=0.5] circle (2pt) [above left] node {\n-1} % display point
   (intersection-2) coordinate (aux-\n-2)
   %[fill=red, opacity=0.5] circle (2pt) [above left] node {\n-2} % display point
   ;
 
 %calculate polar angles
 \pgfmathanglebetweenpoints{\pgfpointanchor{#3}{center}}{%
  \pgfpointanchor{aux-#1-1}{center}} 
 \let\aan\pgfmathresult
  
 \pgfmathanglebetweenpoints{\pgfpointanchor{#3}{center}}{%
  \pgfpointanchor{aux-#1-2}{center}} 
 \let\ban\pgfmathresult
 
 \pgfmathanglebetweenpoints{\pgfpointanchor{#3}{center}}{%
  \pgfpointanchor{aux-#2-1}{center}} 
 \let\can\pgfmathresult
 
 \pgfmathanglebetweenpoints{\pgfpointanchor{#3}{center}}{%
  \pgfpointanchor{aux-#2-2}{center}} 
 \let\dan\pgfmathresult 
 
 % Find the starting point and the angles
 % This is tricky when the node center is around O°
 
 \pgfmathparse{(\ban-\aan)<180 ? 1 : 2}
 \edef\startpoint{aux-#1-\pgfmathresult}
 \pgfmathparse{(\ban-\aan)<180 ? \aan : \ban}
 \let\startan\pgfmathresult
 \pgfmathparse{(\dan-\can)<180 ? \dan : \can}
 \let\endan\pgfmathresult
  
 % Draw the arrow
 % we have to compute radius with \p1 and \n1 (see https://tikz.dev/tutorial-Euclid#autosec-263)
 \draw [pic actions] (\startpoint) let 
                            \p1 = ($ (#1) - (#3) $),
                            \n1 = {veclen(\x1,\y1)}
                          in
                            arc[start angle=\startan,end angle=\endan,radius=\n1];
}}}

\begin{document}

\begin{figure}\centering\begin{tikzpicture}[node distance=2cm,
    mynode/.style={align=center, draw=black,
        % this automatically saves the boundary path of the node
        name path global=aux-path-\csname tikz@fig@name\endcsname},
    arrow/.style={thick,-{Latex[bend]}}]
 
 \def \orig {90} % so we can change the origin without other things
 \def \radius {4cm}
 \coordinate (I) at (0,0); %TODO:use this center for polar coordinate so we can translate the whole diagram

 { [start chain=loop placed {at=(\orig-\tikzchaincount*90+90:\radius)}]
  % draw nodes on chain
  \node foreach \s in {circle, isosceles triangle, rectangle, trapezium}
   [mynode, \s, on chain] (\s) {\s};
 }
  % draw arrows between nodes
  % a perhaps more tikz way
 \path[every pic/.style=arrow]
    pic{arc arrow=from circle to isosceles triangle with center at I}
    pic{arc arrow=from isosceles triangle to rectangle with center at I}
    pic{arc arrow=from rectangle to trapezium with center at I}
    pic{arc arrow=from trapezium to circle with center at I};
%   
% one can do this also with one loop
%
%  \path foreach \s [remember=\s as \t (initially trapezium)]
%   in {circle, isosceles triangle, rectangle, trapezium}
%   {
%   pic[arrow]{arc arrow=from {\t} to {\s} with center at I}
%   };
    
\end{tikzpicture}
\caption{The most robust and pretty cycle created by Ti\textit{k}Z i.e. \LaTeX, at the expense of using intersections package and doing angles calculation.}\end{figure}
\end{document}
3
  • 1
    Please keep in mind that there is no guarantee that the next path will be the background path in \node, so name path might return the wrong thing. Aug 1 at 7:53
  • Thanks a lot @Frodo. Making it more TikZ-style was my next goal. Could-you explain why you use bend arrow's style ? It seems to me that as we draw arrows from true arcs, there is no "bend".
    – ChrisAga
    Aug 1 at 9:46
  • 1
    I think the arrow head is bent. To see that it is bent, try e.g. arrow/.style={thick,-{Latex[bend,length=2.2cm,width=3mm]}}. Of course, this looks ugly, but it shows clearly that the arrow heads are bent. (Note also that loading bending also installs flex, which avoids distorting the curve, i.e. the arc in this case.)
    – user277428
    Aug 1 at 9:53
1

The issue with needing name path global (rather than just name path) is because you create the nodes in the chain inside a foreach loop, via the \node foreach syntax. foreach loops execute their code inside TeX groups, so any macros created non-globally get lost. If you're using pgfplots then that defines \pgfplotsforeachungrouped which is (I understand) a drop-in replacement for \foreach.

The following code is an alternative strategy for your outcome. It starts with a circle through all the nodes and then uses the spath3 library to manipulate that circle to create the arcs. It takes the circle and splits it into components where the circle intersects with the node paths. Then the components inside the nodes are thrown away, and lastly the remaining pieces are rendered as separate paths (so that they all pick up an arrowhead).

\documentclass{article}

%\url{https://tex.stackexchange.com/q/652566/86}

\usepackage{tikz}
\usetikzlibrary{
  intersections,
  chains,
  scopes,
  calc,
  shapes.geometric,
  spath3
}


% This defines a version of `\foreach` that doesn't introduce groups.
% By using the original `\foreach` to parse the list specification,
% we can allow everything that is legal for `\foreach`.
%
% The various optional parts of `\foreach` aren't supported
\ExplSyntaxOn

\clist_new:N \g__foreach_clist
\tl_new:N \l__foreach_tl

\DeclareDocumentCommand \UngroupedForeach {m m m}
{
  \clist_gclear:N \g__foreach_clist
  \foreach \l__foreach_tl in {#2}
  {
    \clist_gput_right:NV \g__foreach_clist \l__foreach_tl
  }
  \clist_map_inline:Nn \g__foreach_clist
  {
    \tl_set:Nn #1 {##1}
    #3
  }
  \clist_gclear:N \g__foreach_clist
}

\ExplSyntaxOff

\begin{document}

\begin{figure}
\centering
% `\tikzstyle` is old syntax and depreciated
\begin{tikzpicture}[
  node distance=2cm,
  my node/.style={
    align=center,
    draw=black
  },
  arrow/.style={
    draw,
    thick,
    ->,
    >=latex
  }
]

 
 \def \orig {90} % so we can change the origin without other things
 \def \radius {4cm}
 \coordinate (I) at (1,2);

% These show that the diagram is translated to be centred at (I)
\fill[red] (0,0) circle[radius=7pt];
\fill[green] (I) circle[radius=5pt];


\begin{scope}[
  shift=(I), % This places the local origin at (I)
  start chain=loop placed {at=(\orig-\tikzchaincount*90+90:\radius)}
]

% Draw nodes on chain.
% We use the ungrouped foreach to avoid the fact that PGF's `\foreach`
% works inside groups and so the `name path`s are lost outside the loop
\UngroupedForeach \s {circle, isosceles triangle, rectangle, trapezium}
{
  \node
  [my node, \s, on chain, name path = loop-\tikzchaincount] {\s};
}

% This next section draws the arrows

% This will form the basis for the arrow paths
\path[spath/save=arrow path] (0,0) circle[radius=\radius];

% Now we split it where it intersects with each node path
% We use the ungrouped foreach so that the splits are
% remembered outside the loop
\UngroupedForeach \k {1,...,\tikzchaincount}
{
  \tikzset{
    spath/split at intersections with={arrow path}{loop-\k}
  }
}

% We're going to throw away alternate components, since half
% the components are *between* the nodes and half are *inside*
% the nodes.  This just needs to be big enough that all
% components are considered.
\pgfmathsetmacro\LastEntry{2*\tikzchaincount}

\tikzset{
  % In this case we remove the even components, if the nodes
  % started at a different angle it might be that we'd have
  % to remove the odd components
  spath/remove components={arrow path}{2,4,...,\LastEntry},
  % If we don't do this, we get a spurious arrow at the centre
  % of the diagram
  spath/remove empty components={arrow path},
  % Specify a style for each component
  every spath component/.style={arrow},
  % Render each component as a separate path (meaning that each
  % picks up an arrow)
  spath/render components={arrow path}
}

\end{scope}
 
\end{tikzpicture}
\caption{The most robust and pretty cycle created by Ti\textit{k}Z
  i.e. \LaTeX, at the expense of using the spath3 package.}
\end{figure}

\end{document}

Nodes with arrows between them

(The coloured circles show that the picture has been shifted, which seemed to be also something you were wanting to do.)

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.