6

I'd like to compute the intersection between a node and a path, here I'd like the coordinate of the intersection in the green box:

enter image description here

My goal is to define a style that draws lines similar to \draw[mystyle] (A) to (E);, except that the line starts at the green position. I saw \pgfpointshapeborder{hnodei}{hpointi} but it's only between the center of the node and a point, here I want from a point which is not in the center of the node.

What I don't want: I don't want to manually fake this style using A.20 or similar; I want to programmatically compute this value, so that I can use it later in complex styles.

Thanks.

MWE:

\documentclass[options]{article}

\author{}
\usepackage{tikz-cd}
\usetikzlibrary{shapes.misc, positioning,calc}
\begin{document}
\begin{tikzpicture}[
  myC/.style={
    to path={(\tikztostart.west) -- (\tikztotarget) \tikztonodes}
  }
  ]
  \node[rounded rectangle, draw] (A) at (0,0) {ABCD};
  \node[circle, draw] (E) at (1,-1) {E};
  \draw[thick,red] (E.center) -- (A.center);
  \draw[] (E) -- (A);
  \draw[thick,blue] ($(A.north east)!.5!(A.south east)$) -- (E.center);
\end{tikzpicture}

\end{document}

EDIT Using this code, I can define a new anchor named fake center east at the beginning of the blue line:

\def\zx@pgfaddtoshape#1#2{%
  \begingroup
  \def\pgf@sm@shape@name{#1}%
  \let\anchor\pgf@sh@anchor
  #2%
  \endgroup
}
\def\zx@useanchor#1#2{\csname pgf@anchor@#1@#2\endcsname}
\zx@pgfaddtoshape{rounded rectangle}{
  \anchor{fake center east}{%
    \zx@useanchor{rounded rectangle}{north east}%
    \pgf@yc=.5\pgf@y% final y = 0.5*this y + 0.5*other y.
    \zx@useanchor{rounded rectangle}{south east}%
    \pgf@y=.5\pgf@y%
    \advance\pgf@y by \pgf@yc%
  }%
}

However, my issue is that I'm not sure how to combine it with the intersection library in order to create a style. I tried:

\documentclass[]{article}

\usepackage{tikz-cd}
\usetikzlibrary{calc}
\usetikzlibrary{shapes.misc, positioning,intersections}

\begin{document}

\makeatletter

\tikzset{
  defaultNodeStyle/.code={
    \pgfkeysalso{
      shape=rounded rectangle,
      draw,
      anchor=center,
    }
  },
  N/.style={
    to path={\pgfextra{%
        \pgfintersectionofpaths{%
          %%%% /!\ This lines is the one I'm not sure how to write:
          \pgfsetpath{tikz@intersect@path@name@ABCD}
        }{%
          \pgfpathmoveto{\pgfpointanchor{\tikztostart}{center}}%
          \pgfpathlineto{\pgfpointanchor{\tikztotarget}{center}}%
        }
        \pgfpointintersectionsolution{1}
        \def\zx@tikztostart{\pgf@x,\pgf@y}%
      } (\zx@tikztostart) -- (tikztotarget) \tikztonodes}
  },
}

\begin{tikzcd}
  %% Right now we fix the path --v, later this must be automatically added (\tikz@fig@name is always defined in a matrix... hopefully we are.)
  |[defaultNodeStyle,name path=ABCD,alias=X]| ABCD \ar[rd,N] & |[defaultNodeStyle]| EFG\\
  |[defaultNodeStyle]| ABCD & |[defaultNodeStyle]| EFG
\end{tikzcd}
\makeatother


\end{document}

But seems like \pgfintersectionofpaths{\pgfsetpath{tikz@intersect@path@name@ABCD}}{my other path} is not the good syntax...

This other try compiles, but result is bad:

\documentclass[]{article}

\usepackage{tikz-cd}
\usetikzlibrary{calc}
\usetikzlibrary{shapes.misc, positioning,intersections}

\begin{document}

\makeatletter

\tikzset{
  defaultNodeStyle/.code={
    \pgfkeysalso{
      shape=rounded rectangle,
      draw,
      anchor=center,
    }
  },
  N/.style={
    to path={\pgfextra{%
        \def\tikz@intersect@path@a{ABCD}%
        \pgfintersectionofpaths{%
          %%%% /!\ This lines is the one I'm not sure how to write:
          \expandafter\pgfsetpath\csname tikz@intersect@path@name@\tikz@intersect@path@a\endcsname
        }{%
          \pgfpathmoveto{\pgfpointanchor{\tikztostart}{center}}%
          \pgfpathlineto{\pgfpointanchor{\tikztotarget}{center}}%
        }
        \pgfpointintersectionsolution{1}
        \def\zx@tikztostart{\pgf@x,\pgf@y}%
      } (\zx@tikztostart) -- (\tikztotarget) \tikztonodes}
  },
}

\begin{tikzcd}
  %% Right now we fix the path --v, later this must be automatically added (\tikz@fig@name is always defined in a matrix... hopefully we are.)
  |[defaultNodeStyle,name path=ABCD,alias=X]| ABCD \ar[rd,N] & |[defaultNodeStyle]| EFG\\
  |[defaultNodeStyle]| ABCD & |[defaultNodeStyle]| EFG
\end{tikzcd}
\makeatother


\end{document}

EDIT

Oh, actually it looks like this is a problem with matrices which does not put the parent node at the good position (see Intersections with border of matrix node):

enter image description here

Does that mean that I've no solution for matrix entries?

\documentclass[]{article}

\usepackage{tikz-cd}
\usetikzlibrary{calc}
\usetikzlibrary{shapes.misc, positioning,intersections}

\begin{document}

\makeatletter

\tikzset{
  defaultNodeStyle/.code={
    \pgfkeysalso{
      shape=rounded rectangle,
      draw,
      anchor=center,
    }
  },
  N/.style={
    to path={\pgfextra{%
        \def\tikz@intersect@path@a{ABCD}%
        \pgfintersectionofpaths{%
          %%%% /!\ This lines is the one I'm not sure how to write:
          \expandafter\pgfsetpath\csname tikz@intersect@path@name@\tikz@intersect@path@a\endcsname
          \pgfgetpath\temppath
          \pgfusepath{stroke}
          \pgfsetpath\temppath
        }{%
          \pgfpathmoveto{\pgfpointanchor{\tikztostart}{center}}%
          \pgfpathlineto{\pgfpointanchor{\tikztotarget}{center}}%
          \pgfgetpath\temppath
          %\pgfusepath{stroke}
          \pgfsetpath\temppath
        }
        \pgfpointintersectionsolution{1}
        \def\zx@tikztostart{\pgf@x,\pgf@y}%
      } (\zx@tikztostart) -- (\tikztotarget) \tikztonodes}
  },
}

It fails with tikzcd (the node ABCD is put at the wrong position):

\begin{tikzcd}
  %% Right now we fix the path --v, later this must be automatically added (\tikz@fig@name is always defined in a matrix... hopefully we are.)
  |[defaultNodeStyle,name path=ABCD]| ABCD \ar[rd,N] & |[defaultNodeStyle]| EFG\\
  |[defaultNodeStyle]| ABCD & |[defaultNodeStyle]| EFG
\end{tikzcd}

But works with tikz pictures:
\begin{tikzpicture}
  \node[defaultNodeStyle,name path=ABCD] (A) {ABCD};
  \node[defaultNodeStyle] (F) at (1,-1) {F};
  \draw[red] (A) to[red,<->,N] (F);
\end{tikzpicture}

\makeatother

\end{document}

I also tried to configure automatically the name path using name path/.expand once=zx@path@\tikz@fig@name, but LaTeX freezes:

\documentclass[]{article}

\usepackage{tikz-cd}
\usetikzlibrary{calc}
\usetikzlibrary{shapes.misc, positioning,intersections}

\begin{document}

\makeatletter

\tikzset{
  defaultNodeStyle/.code={
    name path/.expand once=zx@path@\tikz@fig@name,
    \pgfkeysalso{
      shape=rounded rectangle,
      draw,
      anchor=center,
    }
  },
  N/.style={
    to path={\pgfextra{%
        \def\tikz@intersect@path@a{ABCD}%
        \pgfintersectionofpaths{%
          %%%% /!\ This lines is the one I'm not sure how to write:
          \expandafter\pgfsetpath\csname tikz@intersect@path@name@zx@path@\tikztostart\endcsname
          \pgfgetpath\temppath
          \pgfusepath{stroke}
          \pgfsetpath\temppath
        }{%
          \pgfpathmoveto{\pgfpointanchor{\tikztostart}{center}}%
          \pgfpathlineto{\pgfpointanchor{\tikztotarget}{center}}%
          \pgfgetpath\temppath
          %\pgfusepath{stroke}
          \pgfsetpath\temppath
        }
        \pgfpointintersectionsolution{1}
        \def\zx@tikztostart{\pgf@x,\pgf@y}%
      } (\zx@tikztostart) -- (\tikztotarget) \tikztonodes}
  },
}

It fails with tikzcd (the node ABCD is put at the wrong position):

\begin{tikzcd}
  |[defaultNodeStyle]| ABCD \ar[rd,N] & |[defaultNodeStyle]| EFG\\
  |[defaultNodeStyle]| ABCD & |[defaultNodeStyle]| EFG
\end{tikzcd}

But works with tikz pictures:
\begin{tikzpicture}
  \node[defaultNodeStyle] (A) {ABCD};
  \node[defaultNodeStyle] (F) at (1,-1) {F};
  \draw[red] (A) to[red,<->,N] (F);
\end{tikzpicture}

\makeatother

\end{document}

EDIT Whooo I managed to correct the fact that the path is centered by shifting everything (I'm not sure why but sometimes when I draw the x axis only is inverted, and then both axis are inverted). I still need to find how to automatically give the name path

enter image description here

\documentclass[]{article}

\usepackage{tikz-cd}
\usetikzlibrary{calc}
\usetikzlibrary{shapes.misc, positioning,intersections}

\begin{document}

\makeatletter
%% Create anchors
\def\zx@pgfaddtoshape#1#2{%
  \begingroup
  \def\pgf@sm@shape@name{#1}%
  \let\anchor\pgf@sh@anchor
  #2%
  \endgroup
}
\def\zx@useanchor#1#2{\csname pgf@anchor@#1@#2\endcsname}
\zx@pgfaddtoshape{rounded rectangle}{
  \anchor{fake center east}{%
    \zx@useanchor{rounded rectangle}{north east}%
    \pgf@yc=.5\pgf@y% final y = 0.5*this y + 0.5*other y.
    \zx@useanchor{rounded rectangle}{south east}%
    \pgf@y=.5\pgf@y%
    \advance\pgf@y by \pgf@yc%
  }%
}



\tikzset{
  defaultNodeStyle/.code={
    \pgfkeysalso{
      shape=rounded rectangle,
      draw,
      anchor=center,
    }
  },
  N/.style={
    to path={\pgfextra{%
        %% We compute the intersection
        \def\tikz@intersect@path@a{ABCD}%
        \pgfintersectionofpaths{%
          %%%% /!\ This lines is the one I'm not sure how to write:
          \expandafter\pgfsetpath\csname tikz@intersect@path@name@\tikz@intersect@path@a\endcsname%
          \pgfgetpath\temppath%
          %\pgfusepath{stroke} % We draw it, useful to debug, and realize the shape is moved.
          \pgfsetpath\temppath%
        }{% The first path is moved to the center... So we need to shift it also here.
          %% Not idea why, but x axis is inverted???
          \pgfextractx{\pgf@xa}{\pgfpointdiff{\pgfpointanchor{\tikztostart}{fake center east}}{\pgfpointanchor{\tikztostart}{center}}}%
          \pgfextracty{\pgf@ya}{\pgfpointdiff{\pgfpointanchor{\tikztostart}{fake center east}}{\pgfpointanchor{\tikztostart}{center}}}%
          \pgfpathmoveto{\pgfpoint{-\pgf@xa}{\pgf@ya}}%
          %% What, no both axis are inverted???
          \pgfextractx{\pgf@xa}{\pgfpointdiff{\pgfpointanchor{\tikztotarget}{center}}{\pgfpointanchor{\tikztostart}{center}}}%
          \pgfextracty{\pgf@ya}{\pgfpointdiff{\pgfpointanchor{\tikztotarget}{center}}{\pgfpointanchor{\tikztostart}{center}}}%
          \pgfpathlineto{\pgfpoint{-\pgf@xa}{-\pgf@ya}}%
          \pgfgetpath\temppath%
          %\pgfusepath{stroke} % We draw it, useful to debug
          \pgfsetpath\temppath%
        }
        \pgfpointintersectionsolution{1}%
        %% Store the intersection (warning: the center of the shape is moved to the center!)
        \edef\zx@relinter@x{\the\pgf@x}%
        \edef\zx@relinter@y{\the\pgf@y}%
        %% Because the shape was moved to center, we shift it back by adding the coord of the shape:
        \pgfextractx\pgf@xa{\pgfpointanchor{\tikztostart}{center}}
        \pgfextractx\pgf@xb{\pgfpointanchor{\tikztostart}{center}}
        % WARNING! pgfmath removes the dimension (converted in pt). Make sure to put them back after
        \pgfmathsetmacro{\zx@inter@x}{\pgf@x+\zx@relinter@x}
        \pgfmathsetmacro{\zx@inter@y}{\pgf@y+\zx@relinter@y}
        \edef\zx@tikztostart{\zx@inter@x pt,\zx@inter@y pt}%
      }
      (\zx@tikztostart) -- (\tikztotarget)
      \tikztonodes}
  },
}

It fails with tikzcd (the node ABCD is put at the wrong position):

\begin{tikzcd}[execute at end picture={
    \node[]at(0,0){};
  }]
  %% Right now we fix the path --v, later this must be automatically added (\tikz@fig@name is always defined in a matrix... hopefully we are.)
  |[defaultNodeStyle,rounded rectangle west arc=concave,name path=ABCD]| ABCD
  \ar[->,rd,N,line width=.5mm,red]
  \ar[rd,start anchor=fake center east,end anchor=center]
  & |[defaultNodeStyle]| EFG\\
  |[defaultNodeStyle]| ABCD & |[defaultNodeStyle]| EFG
\end{tikzcd}

But works with tikz pictures:
\begin{tikzpicture}
  \node[defaultNodeStyle,name path=ABCD] (A) {ABCD};
  \node[defaultNodeStyle] (F) at (1,-1) {F};
  \draw[red] (A) to[red,<->,N] (F);
\end{tikzpicture}

\makeatother

\end{document}

EDIT The code works now completely for matrices! But it fails for nodes, I'm not sure why but \tikz@fig@name is not defined in that case...

\documentclass[]{article}

\usepackage{tikz-cd}
\usetikzlibrary{calc}
\usetikzlibrary{shapes.misc, positioning,intersections}

\begin{document}

\makeatletter
%% Create anchors
\def\zx@pgfaddtoshape#1#2{%
  \begingroup
  \def\pgf@sm@shape@name{#1}%
  \let\anchor\pgf@sh@anchor
  #2%
  \endgroup
}
\def\zx@useanchor#1#2{\csname pgf@anchor@#1@#2\endcsname}
\zx@pgfaddtoshape{rounded rectangle}{
  \anchor{fake center east}{%
    \zx@useanchor{rounded rectangle}{north east}%
    \pgf@yc=.5\pgf@y% final y = 0.5*this y + 0.5*other y.
    \zx@useanchor{rounded rectangle}{south east}%
    \pgf@y=.5\pgf@y%
    \advance\pgf@y by \pgf@yc%
  }%
}



\tikzset{
  defaultNodeStyle/.code={
    \edef\zx@name@path{name@path@\tikz@fig@name}
    \message{HHH \zx@name@path}
    \pgfkeysalso{
      shape=rounded rectangle,
      draw,
      anchor=center,
      name path=\zx@name@path,
    }
  },
  N/.style={
    to path={\pgfextra{%
        %% We compute the intersection
        \edef\tikz@intersect@path@a{name@path@\tikztostart}%
        \message{JJJJ \tikz@intersect@path@a}%
        \pgfintersectionofpaths{%
          %%%% /!\ This lines is the one I'm not sure how to write:
          \expandafter\pgfsetpath\csname tikz@intersect@path@name@\tikz@intersect@path@a\endcsname%
          \pgfgetpath\temppath%
          %\pgfusepath{stroke} % We draw it, useful to debug, and realize the shape is moved.
          \pgfsetpath\temppath%
        }{% The first path is moved to the center... So we need to shift it also here.
          %% Not idea why, but x axis is inverted???
          \pgfextractx{\pgf@xa}{\pgfpointdiff{\pgfpointanchor{\tikztostart}{fake center east}}{\pgfpointanchor{\tikztostart}{center}}}%
          \pgfextracty{\pgf@ya}{\pgfpointdiff{\pgfpointanchor{\tikztostart}{fake center east}}{\pgfpointanchor{\tikztostart}{center}}}%
          \pgfpathmoveto{\pgfpoint{-\pgf@xa}{\pgf@ya}}%
          %% What, no both axis are inverted???
          \pgfextractx{\pgf@xa}{\pgfpointdiff{\pgfpointanchor{\tikztotarget}{center}}{\pgfpointanchor{\tikztostart}{center}}}%
          \pgfextracty{\pgf@ya}{\pgfpointdiff{\pgfpointanchor{\tikztotarget}{center}}{\pgfpointanchor{\tikztostart}{center}}}%
          \pgfpathlineto{\pgfpoint{-\pgf@xa}{-\pgf@ya}}%
          \pgfgetpath\temppath%
          %\pgfusepath{stroke} % We draw it, useful to debug
          \pgfsetpath\temppath%
        }
        \pgfpointintersectionsolution{1}%
        %% Store the intersection (warning: the center of the shape is moved to the center!)
        \edef\zx@relinter@x{\the\pgf@x}%
        \edef\zx@relinter@y{\the\pgf@y}%
        %% Because the shape was moved to center, we shift it back by adding the coord of the shape:
        \pgfextractx\pgf@xa{\pgfpointanchor{\tikztostart}{center}}
        \pgfextractx\pgf@xb{\pgfpointanchor{\tikztostart}{center}}
        % WARNING! pgfmath removes the dimension (converted in pt). Make sure to put them back after
        \pgfmathsetmacro{\zx@inter@x}{\pgf@x+\zx@relinter@x}
        \pgfmathsetmacro{\zx@inter@y}{\pgf@y+\zx@relinter@y}
        \edef\zx@tikztostart{\zx@inter@x pt,\zx@inter@y pt}%
      }
      (\zx@tikztostart) -- (\tikztotarget)
      \tikztonodes}
  },
}

It fails with tikzcd (the node ABCD is put at the wrong position):

\begin{tikzcd}[execute at end picture={
    \node[]at(0,0){};
  }]
  %% Right now we fix the path --v, later this must be automatically added (\tikz@fig@name is always defined in a matrix... hopefully we are.)
  |[defaultNodeStyle,rounded rectangle west arc=concave]| ABCD
  \ar[->,rd,N,line width=.5mm,red]
  \ar[rd,start anchor=fake center east,end anchor=center]
  & |[defaultNodeStyle]| EFG\\
  |[defaultNodeStyle]| ABCD & |[defaultNodeStyle]| EFG
\end{tikzcd}

% But works with tikz pictures:
% \begin{tikzpicture}
%   \node[defaultNodeStyle] (A) {ABCD};
%   \node[defaultNodeStyle] (F) at (1,-1) {F};
%   \draw[red] (A) to[red,<->,N] (F);
% \end{tikzpicture}

\makeatother

\end{document}
10
  • You've been asking many interesting and difficult questions recently. Think I'd learn a lot from them. Sorry, I know this doesn't help but I wish you luck with your big project.
    – SebGlav
    Oct 17, 2021 at 9:56
  • Why not just use something like \draw[thick,blue] (A.340) -- (E.center);? Oct 17, 2021 at 10:24
  • 1
    @SebGlav Ahah thanks a lot ^^ I hope I don't spam too much this forum :-P
    – tobiasBora
    Oct 17, 2021 at 10:57
  • @HenriMenke : the reason is that the angle A.340 will be changing depending on the size of the node (just add a few letters you will see A.340 don't work anymore), and I want the user to use minimal configuration to obtain a good looking result. Also, I want a consistant look of the figures across the document, and if the users needs to tweek angles for every node the result will look akwards.
    – tobiasBora
    Oct 17, 2021 at 11:03
  • 1
    You can use the intersections library with the border of a node, if I remember right then you might need to use the global option when saving the path but otherwise it's all straightforward. Oct 17, 2021 at 13:42

2 Answers 2

3

Here's a solution that uses a new shape rounded rectangle X that returns the point on the rounded when the “external point” is to the left or to the right. I hope I've caught most of the weird cases here but I'm not sure.

I certainly didn't deal with concave arcs but one could add checks for that, too.

Now, this would be all we had to do if we only connect a rounded rectangle X with a coordinate or a node that isn't a rounded rectangle X because when TikZ asks for the border point of a shape it gives the direction to the other node's center. But that's not what we want!

This is why the rr path style checks if both given coordinates are both nodes of the shape rounded rectangle X and only then it construct the to path

-- (<point between the opposite centers of both nodes) -- (\tikztotarget)

At this point, I'm assuming the nodes are either aligned directly vertical or – since you want to use this in a matrix/CD – in a different column.

Otherwise, it does funny things:
enter image description here
That kink is directly halfway between the center west of the upper and center east of the lower node. We'd need to chenk if the arc's center are in the same left-to-right direction as the nodes themselves.

You can use different rounded rectangle arc lengths because the shape checks whether the calculated point is actually on the arc part of the border (by testing against the diagonal compass anchors).

Again, for CDs that only places these nodes (without rotation) in a grid this should work.

Code

\documentclass[tikz]{standalone}
\usetikzlibrary{cd,shapes.misc}
\makeatletter
\def\pgfaddtoshape#1#2{%
  \begingroup
    \def\pgf@sm@shape@name{#1}%
    #2%
  \endgroup}
% Let's add two new anchors:
%  1. the center of the arc in the west
%  2. and the center of the arc in the east
\pgfaddtoshape{rounded rectangle}{%
  \pgf@sh@anchor{center west}{%
    \roundedrectanglepoints
    \ifx\westarc\pgf@lib@sh@misc@rr@text@convex
      \pgf@process{\csname pgf@anchor@rounded rectangle@west\endcsname}
      \advance\pgf@x by \radius
      \advance\pgf@x by \outerxsep
    \else
       \pgfpointadd{\centerpoint}{\pgfpoint{-\halfwidth-\radius}{+0pt}}%
    \fi
  }%
  \pgf@sh@anchor{center east}{%
    \roundedrectanglepoints
    \ifx\eastarc\pgf@lib@sh@misc@rr@text@convex
      \pgf@process{\csname pgf@anchor@rounded rectangle@east\endcsname}
      \advance\pgf@x by -\radius
      \advance\pgf@x by -\outerxsep
    \else
       \pgfpointadd{\centerpoint}{\pgfpoint{\halfwidth+\radius}{+0pt}}%
    \fi
  }%
}
% Let's declare a new shape and inherit everything from the original
% except for the anchorborder
\pgfdeclareshape{rounded rectangle X}{%
  \inheritsavedanchors[from=rounded rectangle]
  \inheritbehindbackgroundpath[from=rounded rectangle]
  \inheritbackgroundpath[from=rounded rectangle]
  \inheritbeforebackgroundpath[from=rounded rectangle]
  \inheritbehindforegroundpath[from=rounded rectangle]
  \inheritforegroundpath[from=rounded rectangle]
  \inheritbeforeforegroundpath[from=rounded rectangle]
  \inheritanchor[from=rounded rectangle]{center}
  \inheritanchor[from=rounded rectangle]{text}
  \inheritanchor[from=rounded rectangle]{mid}
  \inheritanchor[from=rounded rectangle]{mid west}
  \inheritanchor[from=rounded rectangle]{mid east}
  \inheritanchor[from=rounded rectangle]{base}
  \inheritanchor[from=rounded rectangle]{base west}
  \inheritanchor[from=rounded rectangle]{base east}
  \inheritanchor[from=rounded rectangle]{north}
  \inheritanchor[from=rounded rectangle]{south}
  \inheritanchor[from=rounded rectangle]{east}
  \inheritanchor[from=rounded rectangle]{west}
  \inheritanchor[from=rounded rectangle]{north west}
  \inheritanchor[from=rounded rectangle]{north east}
  \inheritanchor[from=rounded rectangle]{south west}
  \inheritanchor[from=rounded rectangle]{south east}
  \inheritanchor[from=rounded rectangle]{rect west}
  \inheritanchor[from=rounded rectangle]{rect east}
  \inheritanchor[from=rounded rectangle]{center west}
  \inheritanchor[from=rounded rectangle]{center east}
  \inheritanchorborder[from=rounded rectangle]
  % Approach:
  % we check whether the external point is to the left or to the right
  % if it's to the left we calculate the borderpoint of the circle
  % (or ellipse when outer seps are different)
  % with center .center west and radius \radius + \outer[xy]sep
  \anchorborder{%
    % save both the given external point (just a direction)
    \pgfextract@process\externalPoint{}%
    % as well as the one in the cs of the node
    \pgfextract@process\externalpoint{% this is weird but okay
      \pgfpointadd{\centerpoint}{\externalPoint}%
    }%
    %
    \roundedrectanglepoints
    \pgf@xa=\pgf@x \pgf@ya=\pgf@y % externalpoint is in xa, ya
    \pgf@process{\csname pgf@anchor@rounded rectangle X@north west\endcsname}%
    \pgf@xb=\pgf@x % west point is in xb
    \pgf@process{\csname pgf@anchor@rounded rectangle X@north east\endcsname}%
    \pgf@xc=\pgf@x % east point is in xc, yc
    \ifdim\pgf@xa<\pgf@xb % external point is to the left
      \pgfpointborderellipse{%
        \pgfpointdiff
          {\csname pgf@anchor@rounded rectangle X@center west\endcsname}
          {\externalpoint}%
        }{%
          \pgfpoint{\radius+\outerxsep}{\radius+\outerysep}%
        }%
        \pgfpointadd{}{\csname pgf@anchor@rounded rectangle X@center west\endcsname}%
        % is the calculated point even on the drawn arc?
        \ifdim\pgf@x<\pgf@xb
          % ok, proceed
        \else
          % let's use the normal border instead
          \csname pgf@anchor@rounded rectangle@border\endcsname{\externalPoint}%
        \fi
    \else
      \ifdim\pgf@xa>\pgf@xc % to the right
        \pgfpointborderellipse{%
          \pgfpointdiff
            {\csname pgf@anchor@rounded rectangle X@center east\endcsname}
            {\externalpoint}%
          }{%
            \pgfpoint{\radius+\outerxsep}{\radius+\outerysep}%
          }%
          \pgfpointadd{}{\csname pgf@anchor@rounded rectangle X@center east\endcsname}%
          % is the calculated point even on the drawn arc?
          \ifdim\pgf@x>\pgf@xc
            % ok, proceed
          \else
            % let's use the normal border instead
            \csname pgf@anchor@rounded rectangle@border\endcsname{\externalPoint}%
          \fi
      \else
        % let's use the normal border
        \csname pgf@anchor@rounded rectangle@border\endcsname{\externalPoint}%
      \fi
    \fi
  }
}

\def\tikz@roundedrectanglex@text{rounded rectangle X}%
\tikzset{
  rr path/.style={
    to path={%
      \pgfextra
      \def\tikz@to@path{-- (\tikztotarget)}% default
      \pgf@process{\tikz@scan@one@point\pgfutil@firstofone(\tikztostart)\relax}%
      \pgf@xa=\pgf@x \pgf@ya=\pgf@y % start is xa, ya
      \iftikz@shapeborder % is start a node?
        \expandafter\ifx\csname pgf@sh@ns@\tikz@pp@name{\tikztostart}\endcsname\tikz@roundedrectanglex@text
          % this is a rrX!
          \pgf@process{\tikz@scan@one@point\pgfutil@firstofone(\tikztotarget)\relax}%
          \pgf@xb=\pgf@x \pgf@yb=\pgf@y % target is xb, yb
          \iftikz@shapeborder % is target a node?
            \expandafter\ifx\csname pgf@sh@ns@\tikz@pp@name{\tikztotarget}\endcsname\tikz@roundedrectanglex@text
              % they're both a rrX
              % check if they're vertical
              \pgf@xc=\pgf@xb\advance\pgf@xc-\pgf@xa
              \ifdim\pgf@xc<0pt \pgf@xc=-\pgf@xc\fi
              \ifdim\pgf@xc<1pt
                % they're vertical!
              \else
                \ifdim\pgf@xa<\pgf@xb % start left of target
                  \pgf@process{\pgfpointlineattime{+.5}
                    {\tikz@scan@one@point\pgfutil@firstofone(\tikztostart.center east)\relax}
                    {\tikz@scan@one@point\pgfutil@firstofone(\tikztotarget.center west)\relax}}%
                  \edef\middlepoint{\the\pgf@x,\the\pgf@y}%
                \else
                  \pgf@process{\pgfpointlineattime{+.5}
                    {\tikz@scan@one@point\pgfutil@firstofone(\tikztostart.center west)\relax}
                    {\tikz@scan@one@point\pgfutil@firstofone(\tikztotarget.center east)\relax}}%
                  \edef\middlepoint{\the\pgf@x,\the\pgf@y}%
                \fi
                \edef\tikz@to@path{--(\middlepoint)--(\tikztotarget)}%
              \fi
            \fi
          \fi
        \fi
      \fi
      \expandafter\endpgfextra\tikz@to@path\tikztonodes
    }
  }
}
\makeatother
\tikzset{n/.style={shape=rounded rectangle X, draw, ultra thick, rounded rectangle arc length=180}}
\begin{document}
\begin{tikzcd}
  |[n]| ABCD \ar[rd,rr path] \ar[r,rr path] \ar[d, rr path] & |[n]| EFG\\
  |[n]| ABCDEFGH & |[n]| EFG
\end{tikzcd}
\end{document}

Output

enter image description here

1

This is offered mainly a proof-of-concept. It will probably need adapting for your specific use-case as there are probably assumptions I've made based on your example that wouldn't be valid in more general situations. Nevertheless, it shows that this can be done via intersections.

It also shows how to use \subnodes (from tikzmark) to define parts of a node that can be used as "targets" for paths. In the ABCD node, the D is encased in a \subnode and it can then be referred to as if it were a node in its own right.

With that in place, the technique for placing the connecting line is straightforward: define (and save) the line between the desired targets (in the ABCD case, this is the D subnode, but it could equally be your new anchor) and then cut it where it intersects with each node's boundary path. Once it is cut, the outer pieces can be removed leaving only the part between the nodes.

In the tikz-cd situation, this is all packaged up into a style. In my version, since I'm using \subnodes as targets, it takes two arguments which are the subnodes. These can be left empty in which case it defaults back to the main node. This uses the name prefix facility that wraps a node's name in some extra fluff. At time of writing, to use name prefix with a subnode requires the development version of tikzmark, available from the github link above - this will make its way to CTAN shortly.

The only extra complication with tikz-cd is that the paths that define the nodes get moved later on so the paths that are saved, via the spath3 library, are not the actual paths. Fortunately, the shifts are simple to work out (but this may be where one of my assumptions is made) and the paths adjusted accordingly.

\documentclass{article}
%\url{https://tex.stackexchange.com/q/619274/86}
\usepackage{tikz}
\usetikzlibrary{
  cd,
  shapes.misc,
  positioning,
  calc,
  intersections, % <- to calculate intersections of paths
  tikzmark, % <- for subnodes
  spath3 % <- for path manipulations
}

\makeatletter
\tikzset{
  % This is a useful diagnostic tool that I frequently use
  show node name/.code={%
    \show\tikz@fig@name%
  },
  defaultNodeStyle/.style={
    shape=rounded rectangle,
    draw,
    anchor=center,
    % Save the node's path, needs to be global as nodes
    % have their own scope. Use the node's name as the path name.
    spath/save global/.expand once=\tikz@fig@name,
    % Append the node's name to the subnode name
    name prefix/.append/.expanded=\tikz@fig@name
  },
  % The two arguments allow for subnodes to be used as targets
  % This could be easily be adapted to using the additional anchors instead
  N/.style 2 args={
    to path={%
      \pgfextra{%
        % The target is split into \tikzcd@ar@target and \tikzcd@endanchor
        % the target anchor doesn't have the node prefix/suffix applied
        % and as we need to use the absolute name to access the node path
        % then we need a version with that applied.
        \edef\@arrowtarget{\tikz@pp@name{\tikzcd@ar@target}}%
        % I'm not an expert with tikz-cd so I don't know the circumstances
        % in which \tikzcd@endanchor is *not* empty, but just in case it isn't
        % then we'd better use it. If it is empty then we default to center.
        \ifx\tikzcd@endanchor\pgfutil@empty
        \def\@arrowtargetanchor{center}%
        \else
        \let\@arrowtargetanchor=\tikzcd@endanchor
        \fi
        % Now we construct our path that will become the arrow path.
        % The 'overlay' is probably not needed, but let's be cautious.
        % There's something a bit weird going on with node name
        % prefix/suffices here since \tikztostart does have them applied
        % while \tikzcd@ar@target doesn't, but nevertheless this seems
        % to work. Weird.
        \path[overlay, spath/save=arrow path] (\tikztostart#1.center) -- (\tikzcd@ar@target#2.\@arrowtargetanchor);
        % Now we get to work with our intersections.
        \tikzset{
          % Clone the node paths
          spath/clone={source path}{\tikztostart},
          spath/clone={target path}{\@arrowtarget},
          % The node paths are currently centred at the origin, we
          % need to shift them to their eventual locations.
          % I suspect this is because of them being part of a matrix
          % so if this were used in a non-matrix context these two
          % lines would not be needed.  Presumably there's some
          % "if matrix" test that could be used here
          spath/transform={source path}{shift={(\tikztostart.center)}},
          spath/transform={target path}{shift={(\@arrowtarget.center)}},
          % Now we intersect the arrow path with the node outlines
          % and split the arrow path where it meets the node outlines
          spath/split at intersections with/.expanded={arrow path}{source path},
          spath/split at intersections with/.expanded={arrow path}{target path},
          % I've naively assumed that the arrow path crosses each node path
          % once, but for a proof-of-concept that doesn't seem too bad
          % an assumption
          spath/remove components={arrow path}{1,3}
        }
        % Finally, render the arrow
      } [spath/use=arrow path]
    }
  }
}
\makeatother

\begin{document}
\begin{tikzpicture}[
  % Needed for subnodes to work
  remember picture,
  % avoid name clashes with the tikz-cd environment
  name prefix=pic-,
  tikzmark prefix=pic-
]
% The subnode here basically makes the 'D' into a viable target
  \node[rounded rectangle, draw, spath/save global=abcd] (A) at (0,0) {ABC\subnode{D}{D}};
  \node[circle, draw, spath/save global=e] (E) at (1,-1) {E};
  \draw[ultra thick,red] (E.center) -- (A.center);
\draw (E) -- (A);
% This is the path that we'll use to draw our path, normally we
% wouldn't draw this particular path
\draw[ultra thick,red,spath/save=ed] (E.center) -- (D.center);
% This is the "desired outcome" path
\draw[thick,blue] ($(A.north east)!.5!(A.south east)$) -- (E.center);
% These keys split the connection path where it meets each node boundary
% and then throws away the first and third components, leaving only the middle
\tikzset{
  spath/split at intersections with={ed}{abcd},
  spath/split at intersections with={ed}{e},
  spath/remove components={ed}{1,3}
}
% Lastly, we use the remaining part of the path.
\draw[spath/use={ed},green];
\end{tikzpicture}

\begin{tikzcd}[
  remember picture,
  name prefix=cd-,
  tikzmark prefix=cd-,
]
  |[defaultNodeStyle]| ABC\subnode{D}{D} \ar[rd,N=D{}] & |[defaultNodeStyle]| EFG\\
  |[defaultNodeStyle]| ABCD & |[defaultNodeStyle]| EFG
\end{tikzcd}

\end{document}

Here's the result:

Lines between nodes that are cut where they intersect with the node boundaries

4
  • I've just seen that I wrote the answer at the question about nodes and matrices, so presumably once-upon-a-time I knew how to extract the amount to shift a node-within-a-matrix by which might be more robust than the code above. If that code isn't anywhere obvious, do ask and I'll dig it out. Aug 20 at 19:03
  • Huh, I'm getting a No shape named `cd-tikz@f@1-1-1D' is known. error and an interesting line. Aug 20 at 21:18
  • @Qrrbrbirlbel Are you using the latest tikzmark (as of 3hrs ago)? I updated it so that subnode names used the name prefix/suffix keys. That error suggests that the subnode D isn't picking up the prefix. Aug 20 at 21:50
  • 1
    Oh, whoops. Should have read your answer thoroughly. :| Aug 20 at 23:36

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.