8

Motivated by this recent question, I tried to cook up a tikz based solution using the code provided in the answer to this question. More precisely, I tried to cook up equivalents of the pstricks \rnode and \ncline commands in tikz. This is what I got:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}
\newsavebox{\tempbox}

\newcommand{\tikznode}[2]{\relax\ifmmode\savebox{\tempbox}{\ensuremath{#2}}
\makebox[\wd\tempbox]{\tikz[overlay,remember picture,baseline=-0.8ex]%
 \node[minimum width=\wd\tempbox] (#1) {\ensuremath{#2}};}
\else\savebox{\tempbox}{#2}
\makebox[0.5\wd\tempbox]{\tikz[overlay,remember picture,baseline=-0.8ex,minimum width=\wd\tempbox]%
 \node[minimum width=\wd\tempbox] (#1) {#2};}\fi}

\tikzset{
    ncbar angle/.initial=-90,
    ncbar/.style={
        to path=(\tikztostart)
        -- ($(\tikztostart)!#1!\pgfkeysvalueof{/tikz/ncbar angle}:(\tikztotarget)$)
        -- ($(\tikztotarget)!($(\tikztostart)!#1!\pgfkeysvalueof{/tikz/ncbar angle}:(\tikztotarget)$)!\pgfkeysvalueof{/tikz/ncbar angle}:(\tikztostart)$)
        -- (\tikztotarget)
    },
    ncbar/.default=0.5cm
}

\begin{document}
blabla \tikznode{A}{A}\tikznode{B}{B}\tikznode{C}{C} blabla
\tikznode{X}{X}\tikznode{Y}{Y}\tikznode{Z}{Z} blabla
\tikz[overlay,remember picture]{\draw[->] (A) to [ncbar=0.4] (X);}%
\tikz[overlay,remember picture]{\draw[->] (B) to [ncbar=0.5] (Y);}%
\tikz[overlay,remember picture]{\draw[->] (C) to [ncbar=0.6] (Z);}%
\end{document}

It produces:

enter image description here

which looks not completely unreasonable. However, it seems unreasonably complicated and I assume that there is a much more elegant and stable solution. For instance, I need to shift the baseline by hand using baseline=-0.8ex. This doesn't seem right. I also need to compute the width of the text and multiply it by an arbitrary factor of 0.5, which again appears odd. Last but not least, I have to distinguish between math and text modes. (Yes, I know that in principle there is \mathchoice that may be used, but I feel that I've chosen an unnecessarily complicated approach, so I refrained from building this in.) My question is if someone knows how to make this more elegant and, in particular, stable.

  • Why do you have to distinguish between text and math mode? \tikz works in both, and the mode of the text in the node isn't affected by the mode in which the \tikz is placed. – Torbjørn T. Nov 21 '17 at 21:35
  • @TorbjørnT. because I'd also like to use it in math mode. More precisely, if I type $\tikz{\node(x){x};}$, the x is not in math mode, but with $\tikznode{x}{x}$ it is. –  Nov 21 '17 at 21:42

2 Answers2

7

I would probably do

\newcommand{\tikznode}[2]{%
\ifmmode%
\tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node (#1) {$#2$};%
\else
\tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node (#1) {#2};%
\fi}

By setting the baseline to the base anchor of the node, you don't have to use some specific value. With inner sep=0pt you wont get any extra space around the text in the node.

Note also that I removed the overlay option. That option means that you get a zero-size bounding box, which you don't want here.

output of code

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}

\newcommand{\tikznode}[2]{%
\ifmmode%
\tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node (#1) {$#2$};%
\else
\tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node (#1) {#2};%
\fi}

\tikzset{
    ncbar angle/.initial=-90,
    ncbar/.style={
        to path=(\tikztostart)
        -- ($(\tikztostart)!#1!\pgfkeysvalueof{/tikz/ncbar angle}:(\tikztotarget)$)
        -- ($(\tikztotarget)!($(\tikztostart)!#1!\pgfkeysvalueof{/tikz/ncbar angle}:(\tikztotarget)$)!\pgfkeysvalueof{/tikz/ncbar angle}:(\tikztostart)$)
        -- (\tikztotarget)
    },
    ncbar/.default=0.5cm
}

\begin{document}
blabla \tikznode{A}{A}$\tikznode{B}{B}$\tikznode{C}{C} blabla
\tikznode{X}{X}\tikznode{Y}{Y}\tikznode{Z}{Z} blabla
\begin{tikzpicture}[overlay,remember picture]
\draw[->] (A) to [ncbar=0.4] (X);
\draw[->] (B) to [ncbar=0.5] (Y);
\draw[->] (C) to [ncbar=0.6] (Z);
\end{tikzpicture}
\end{document}

If you want to implement \mathchoice in this, you get problems, because all the four different choices are typeset. Hence, the naive implementation of

\newcommand{\tikznode}[2]{%
\ifmmode%
\mathchoice
  {\tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node (#1) {$\displaystyle #2$};}%
  {\tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node (#1) {$\textstyle #2$};}%
  {\tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node (#1) {$\scriptstyle #2$};}%
  {\tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node (#1) {$\scriptscriptstyle #2$};}%
\else
\tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node (#1) {#2};%
\fi}

doesnt't work, because the node name will always refer to the scriptscript-version, which isn't used at all in the three other styles. So for the example above, where the B is in textstyle, you get an arrow pointing out of the page.

Perhaps unsurprisingly though, someone has wondered about that problem before, and Heiko Oberdiek provided a solution to it in mathchoice and tikz's remember picture. The refmathstyle package he made has not yet been published on CTAN though, and the link to it in that answer is dead, so here is the non-package version:

output of below code

\documentclass{article}
\usepackage{tikz}
\usepackage{refcount}
\usetikzlibrary{calc}

% from https://tex.stackexchange.com/questions/122415/mathchoice-and-tikzs-remember-picture
\makeatletter
\newcounter{tikznode}
\renewcommand*{\thetikznode}{tikznode@\the\value{tikznode}}
\newcommand*{\tikznodestyle}{%
  \refused{\thetikznode}%
  \ifcase\getrefbykeydefault{\thetikznode}{}{0} %
    \displaystyle
  \or\textstyle
  \or\scriptstyle
  \or\scriptscriptstyle
  \fi
}
\newcommand{\tikznode}[2]{%
\ifmmode%
  \stepcounter{tikznode}%
  \mathchoice
  {\def\@currentlabel{0}\label{\thetikznode}}%
  {\def\@currentlabel{1}\label{\thetikznode}}%
  {\def\@currentlabel{2}\label{\thetikznode}}%
  {\def\@currentlabel{3}\label{\thetikznode}}%
  \tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node (#1) {$\tikznodestyle #2$};
\else
  \tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node (#1) {#2};%
\fi}
\makeatother

% from https://tex.stackexchange.com/questions/55068/is-there-a-tikz-equivalent-to-the-pstricks-ncbar-command
\tikzset{
    ncbar angle/.initial=-90,
    ncbar/.style={
        to path=(\tikztostart)
        -- ($(\tikztostart)!#1!\pgfkeysvalueof{/tikz/ncbar angle}:(\tikztotarget)$)
        -- ($(\tikztotarget)!($(\tikztostart)!#1!\pgfkeysvalueof{/tikz/ncbar angle}:(\tikztotarget)$)!\pgfkeysvalueof{/tikz/ncbar angle}:(\tikztostart)$)
        -- (\tikztotarget)
    },
    ncbar/.default=0.5cm
}

\begin{document}
blabla
$\displaystyle\tikznode{A}{A}\textstyle\tikznode{B}{B}_{\tikznode{C}{C}_{\tikznode{D}{D}}}$
blabla
\tikznode{X}{X}\tikznode{Y}{Y}\tikznode{Z}{Z}\tikznode{W}{W}
blabla
\begin{tikzpicture}[overlay,remember picture]
\draw[->] (A) to [ncbar=0.4] (X);
\draw[->] (B) to [ncbar=0.5] (Y);
\draw[->] (C) to [ncbar=0.6] (Z);
\draw[->] (D) to [ncbar=0.6] (W);
\end{tikzpicture}
\end{document}
Torbjørn T.
  • 206,688
  • I wouldn't be surprised if there are a number of caveats I haven't considered, so it this is useless let me know, I can always delete it. – Torbjørn T. Nov 21 '17 at 21:47
  • That's a great trick, so please don't delete it. Do you want to post it also as an answer to this question? (Since even you are a bit struggling with the math mode, perhaps it's really worthwhile to build in \mathchoice.) –  Nov 21 '17 at 21:51
  • @marmot (I rarely/never dig in to lower-level stuff, so there is a lot I don't know.) I've never used \mathchoice, but you get some problems because all four options are typeset, meaning that the node names get overwritten. I can give an impractical workaround for that, I don't know if there are any good ones. – Torbjørn T. Nov 21 '17 at 22:07
  • @marmot By the way, \mathchoice has nothing to with distinguishing between text and math mode, which it seems like you think. – Torbjørn T. Nov 21 '17 at 22:12
  • Hmmh, I thought it would discriminate the different math modes \displaystyle, normal, \scriptstyle and \scriptscriptstyle. Probably I misinterpreted this post. Anyway, you're right, I have a hard time implementing it. –  Nov 21 '17 at 22:18
  • @marmot Yes, but that is a different distinction than math vs. text. – Torbjørn T. Nov 21 '17 at 22:19
  • Yes, what I tried to do is \newcommand{\tikznode}[2]{\relax% \ifmmode\relax\mathchoice{\tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node(#1){\ensuremath{\displaystyle #2}};}{\tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node(#1){\ensuremath{#2}};}{\tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node(#1){\ensuremath{#2}};}% {\tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node(#1){\ensuremath{#2}};} \else \tikz[remember picture,baseline=(#1.base),inner sep=0pt] \node (#1) {#2};% \fi}, but, as you anticipated, hell breaks loose. –  Nov 21 '17 at 22:21
  • @marmot See updated answer, which isn't good, but it's something. – Torbjørn T. Nov 21 '17 at 22:27
  • There is actually an issue: your command does not harmonize with amsmath equation environments. There are error messages of the type ! Package amsmath Error: Multiple \label's: label 'tikznode@3' will be lost.. However, if one continues compiling, the output is OK. –  Nov 22 '17 at 16:47
  • @marmot For that I can safely blame Heiko, the error comes from the code I borrowed from his post. – Torbjørn T. Nov 22 '17 at 17:07
  • I certainly agree. Perhaps that's the reason why he did not write the package. Of course, one may cook up a workaround, but chances are that this will cause conflicts with other packages. –  Nov 22 '17 at 18:13
  • 1
    @marmot I've added a version of \tikznode to the tikzmark library (and renamed it \tikzmarknode). Since remember picture already writes something to the aux file, I use that instead of needing an extra label. I haven't tried working with amsmath as yet. Code is on github. – Andrew Stacey Sep 09 '18 at 20:05
  • First experiment with amsmath seems to work, can you send me some code that doesn't with the above so that I can test? – Andrew Stacey Sep 09 '18 at 20:07
  • @LoopSpace Thanks! (Did you downvote my recent answer to this question? When I renamed the macro there precisely in order to avoid the conflict with your nice library, not to say anything bad to it. If you did not downvote, than this question/answer may be a playground example, and I will be happy to delete my answer to see yours.) –  Sep 09 '18 at 20:10
  • No! I didn't downvote! (can't remember when I last downvoted here, but regardless your answer wouldn't be one that I would). And please don't delete your answer there. It is a good answer. – Andrew Stacey Sep 09 '18 at 20:24
  • I'm trying to use this solution to get ncbars that connect variables in equations. I'm getting bars that come off at not-quite-90-degree angles just like shown in the 2nd picture of this answer. That's not what I want, even if the connected elements have different heights or depths. What's causing it and how can I fix it? – David Wright Oct 01 '19 at 06:04
  • 1
    @DavidWright As for why, that's what ncbar does. The connecting line is parallel to the line between the two points. If the bar should always be horizontal, you could change the style to the much simpler ncbar/.style={to path={(\tikztostart) -- ++(0,-#1) -| (\tikztotarget)}}. – Torbjørn T. Oct 01 '19 at 15:36
  • @TorbjørnT.: Thank you, that's exactly what I needed. – David Wright Oct 02 '19 at 06:11
2

Since it's buried in the comments on Torbjørn's answer, this is just to note that this is now a part of the tikzmark package, and correctly handles the different math modes. Here's a demo:

\documentclass{article}
%\url{https://tex.stackexchange.com/q/402462/86}
\usepackage{tikz}
\usetikzlibrary{tikzmark,calc}

\tikzset{ ncbar angle/.initial=-90, ncbar/.style={ to path=(\tikztostart) -- ($(\tikztostart)!#1!\pgfkeysvalueof{/tikz/ncbar angle}:(\tikztotarget)$) -- ($(\tikztotarget)!($(\tikztostart)!#1!\pgfkeysvalueof{/tikz/ncbar angle}:(\tikztotarget)$)!\pgfkeysvalueof{/tikz/ncbar angle}:(\tikztostart)$) -- (\tikztotarget) }, ncbar/.default=0.5cm }

\begin{document} blabla \tikzmarknode{A}{A}(\tikzmarknode{B}{B}^{\tikzmarknode{C}{C}}) blabla \tikzmarknode{X}{X}\tikzmarknode{Y}{Y}\tikzmarknode{Z}{Z} blabla \begin{tikzpicture}[overlay,remember picture] \draw[->] (A) to [ncbar=0.4] (X); \draw[->] (B) to [ncbar=0.5] (Y); \draw[->] (C) to [ncbar=0.6] (Z); \end{tikzpicture} \end{document}

Demonstration of tikzmarknode with different math modes

The lines from C to Z are angled because the bottoms of the nodes are at different levels. I don't know what the original PSTricks ncline does in that circumstance so I left that code as it is in Torbjørn's answer.

Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751