7

Here's a MWE using tikzmark to draw an arrow connecting two sub-nodes in a tree:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{external}
%\tikzexternalize
\usetikzlibrary{tikzmark}
\begin{document}

\begin{tikzpicture} 
  \node {A} 
    child {node {\subnode{end}{B}}}
    child {node {C \subnode{start}{B} E}}; 
\end{tikzpicture}

\begin{tikzpicture}[
  remember picture,
  overlay]
  \draw[->] (start.south) to [out=south west,in=south east] (end.south);
\end{tikzpicture}

\end{document}

As expected, this yields the following output (calling pdflatex with -shell-escape):

tikzmark works

However, if %\tikzexternalize is un-commented, compilation fails, with the following error:

! Package pgf Error: No shape named start is known.

Are tikzmark and tikz externalize incompatible? If so, are they fundamentally incompatible?

Edited to add: if fundamentally incompatible, is there any alternative way to execute something like the image in this post -- that is, to target a sub-node with (e.g.) an arrow?

SEC
  • 761
  • Almost certainly! Do you need the \draw command to be part of a different tikzpicture? It ought to work if you put it as part of the original one. – Andrew Stacey Jul 15 '15 at 22:59
  • No dice. No compilation errors, but the arrow is not drawn correctly. See here for what results (the "arrow" is at the bottom-right end of the A). – SEC Jul 15 '15 at 23:02
  • 2
    As far as I can tell, yes, they are incompatible. When you externalise, the picture is typeset once. After that, unless the code changes, the previously typeset version is used. At least, this is the typical usage (you can change it). But tikzmark relies on multiple compilations. Maybe there is a way around it, but I now always disable externalisation for pictures which use tikzmark. – cfr Jul 15 '15 at 23:24
  • This is roughly what I figured, though I am still sad to hear it. – SEC Jul 15 '15 at 23:25
  • You need to force it to compile twice which externalize won't naturally do. Use the force remake key. – Andrew Stacey Jul 16 '15 at 06:27
  • Yes, I realize that. Doing so doesn't fix the issue. – SEC Jul 16 '15 at 09:48
  • @LoopSpace I haven't looked into it, but that isn't enough I don't think. Having said that, I've not tried it with \subnode and if it were going to work at all, it would presumably be in that case. (Because the later commands are part of the same picture.) – cfr Jul 16 '15 at 13:01
  • Can you not just not externalise pictures where you use \subnode? – cfr Jul 16 '15 at 13:02
  • @cfr That is an option. But in my case, not a good one, since my trees frequently require arrows targeting sub-nodes. – SEC Jul 16 '15 at 13:04
  • 1
    Okay, the basic problem is that the externalisation library disables writing the aux files for the pictures and tikzmark relies on saving information in the auxfile. So you need to re-enable the auxfile, but even doing that then it doesn't work properly (I get that the arrow is the right size but displaced) probably due to the page cropping. – Andrew Stacey Jul 16 '15 at 17:46
  • Your nodes with subnodes, are they all single line text? (ie no line breaks) – Andrew Stacey Jul 16 '15 at 17:58
  • @LoopSpace not sure exactly what you mean: are they all single-line text in the source code, or in the output? Assuming the latter, the answer is negative; some of my figures look like so. – SEC Jul 16 '15 at 18:06
  • I meant in the output. I was thinking of building up the text as a series of actual nodes (or rather as a multipart node) but I don't think that would be easy with multi-lines. How about putting the pictures in individual files, compiling them separately, and \includegraphicsing them into the master document? – Andrew Stacey Jul 16 '15 at 18:13
  • 1
    @SimonC as you predicted, a little esoteric indeed. – Gonzalo Medina Jul 17 '15 at 01:42

2 Answers2

5

Here's a somewhat hackish way that works with your given example. Try it on some more complicated ones to see how robust it is.

The first issue is that when externalising, TikZ invokes \nofiles and so nothing gets written to the aux file. Since tikzmark relies on stuff written to the aux file, that's a fairly major obstacle to getting it to work with externalisation. To enable that, we steal the idea from https://tex.stackexchange.com/a/75030/86, which also means that we have to use list and make rather than system call when externalising.

That doesn't solve everything, though, because even when stuff is written to the aux file then it isn't written right. There's an offset which might be due to page cropping, but might be due to something else entirely. Fortunately, it is consistent. So what we need to do is put a subnode somewhere known (for example, at the origin) and then use that to compute the offset. Then we can adjust all other subnodes by this offset.

To avoid conflict, I've defined a new subnode command \extsubnode which does this adjustment.

\documentclass{article}
%\url{https://tex.stackexchange.com/q/255543/86}
\usepackage{tikz}
\usetikzlibrary{external}
\let\oldnofiles=\nofiles
\let\nofiles=\relax
\tikzexternalize[mode=list and make]
\let\nofiles=\oldnofiles

\usetikzlibrary{tikzmark}

\makeatletter

\tikzset{
  every picture/.style={
    execute at begin picture={%
      \edef\outermostid{\pgfpictureid}%
      \node {\subnode{origin\outermostid}{}};
    }
  }
}

\newcommand\extsubnode[3][]{%
  \begingroup
  \pgfmark{#2}%
  \setbox\pgfnodeparttextbox=\hbox\bgroup #3\egroup
  \def\tikz@shape{rectangle}%
  \def\tikz@anchor{center}%
  \def\tikz@fig@name{#2}%
  \tikzset{every subnode/.try,#1}%
  \pgfpointorigin
  % there is a spurious space inserted by the next point scan so we
  % box it to ignore it
  \setbox\tikz@tempbox=\hbox\bgroup
  \tikz@scan@one@point\pgfutil@firstofone(origin\outermostid)\relax
  \global\pgf@xa=\pgf@x
  \global\pgf@ya=\pgf@y
  \egroup
  \tikz@scan@one@point\pgfutil@firstofone(pic cs:#2)\relax
  \advance\pgf@x by -\pgf@xa
  \advance\pgf@y by -\pgf@ya
  \advance\pgf@x by .5\wd\pgfnodeparttextbox
  \advance\pgf@y by .5\ht\pgfnodeparttextbox
  \advance\pgf@y by -.5\dp\pgfnodeparttextbox
  \pgftransformshift{}%
  \setbox\@tempboxa=\hbox\bgroup
  \pgfutil@ifundefined{pgf@sh@s@\tikz@shape}%
  {\PackageError{pgf}{Unknown shape ``\tikz@shape''}{}}%
  {%
    {%
      \let\pgf@sh@savedmacros=\pgfutil@empty% MW
      \let\pgf@sh@savedpoints=\pgfutil@empty%
      \def\pgf@sm@shape@name{\tikz@shape}% CJ % TT added prefix!
      \csname pgf@sh@s@\tikz@shape\endcsname%
      \pgf@sh@savedpoints%
      \pgf@sh@savedmacros% MW
      \pgftransformshift{%
        \pgf@sh@reanchor{\tikz@shape}{\tikz@anchor}%
        \pgf@x=-\pgf@x%
        \pgf@y=-\pgf@y%
      }%
      \expandafter\pgfsavepgf@process\csname pgf@sh@sa@\tikz@fig@name\endcsname{%
       \pgf@sh@reanchor{\tikz@shape}{\tikz@anchor}% FIXME : this is double work!
      }%
      % Save the saved points and the transformation matrix
      \edef\pgf@node@name{\tikz@fig@name}%
      \ifx\pgf@node@name\pgfutil@empty%
      \else%
        \expandafter\xdef\csname pgf@sh@ns@\pgf@node@name\endcsname{\tikz@shape}%
        \edef\pgf@sh@@temp{\noexpand\gdef\expandafter\noexpand\csname pgf@sh@np@\pgf@node@name\endcsname}%
        \expandafter\pgf@sh@@temp\expandafter{\pgf@sh@savedpoints}%
        \edef\pgf@sh@@temp{\noexpand\gdef\expandafter\noexpand\csname pgf@sh@ma@\pgf@node@name\endcsname}% MW
        \expandafter\pgf@sh@@temp\expandafter{\pgf@sh@savedmacros}% MW
        \pgfgettransform\pgf@temp
        \expandafter\xdef\csname pgf@sh@nt@\pgf@node@name\endcsname{\pgf@temp}%
        \expandafter\xdef\csname pgf@sh@pi@\pgf@node@name\endcsname{\pgfpictureid}%
      \fi%
    }%
  }%
  \egroup
  \box\pgfnodeparttextbox
  \endgroup
}


\makeatother



\begin{document}

\begin{tikzpicture}
  \node {A} 
    child {node[draw] {\extsubnode{end}{B}}}
    child {node {C\extsubnode{start}{B}E}}; 
  \draw[->] (start.south) to [out=south
      west,in=south east] (end.south);
\end{tikzpicture}

\end{document}
Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751
  • Thank you! Afraid it's not working for me: fails with xelatex, and even with pdflatex, the output is incorrect. – SEC Jul 17 '15 at 00:09
  • xelatex has other issues. With pdflatex, did you run the make command twice? – Andrew Stacey Jul 17 '15 at 06:04
  • Afraid so. If I call make -f, it tells me All images exist now. Use make -B to re-generate them. If I call make -B, it tells me make: Nothing to be done for test.makefile. – SEC Jul 17 '15 at 11:24
  • You need to do make -B -f filename. Though thinking about it, it might be best to modify the makefile to do two runs on each figure. – Andrew Stacey Jul 17 '15 at 14:55
2

Submitted for your consideration. Here is a MWE that gets pretty close by using standalone in lieu of external. The main file (test.tex) looks like so:

\documentclass{article}
\usepackage[mode=buildnew]{standalone}
\begin{document} 
\includestandalone{tree}
\end{document}

And tree.tex looks like so (essentially pasting the answer given by Gonzalo Medina here):

\documentclass[border=20pt]{standalone}

%necessary for tikzmark and xetex to play nicely
\newcount\pdftexversion
\pdftexversion140 \def\pgfsysdriver{pgfsys-dvipdfm.def}

\usepackage{forest}
\usetikzlibrary{tikzmark}
\begin{document}

\begin{forest}
for tree={s sep=20pt}
[A t\subnode{endc}{e}st text
  [B
    [\subnode{enda}{C},
    ] 
  ]
  [D
    [\subnode{endb}{E}
    ]
    [\subnode{startc}{F}
      [G
      ]
      [H
        [\subnode{startb}{John} see \subnode{starta}{x}
        ]
      ] 
    ]
  ] 
]
\end{forest}

\begin{tikzpicture}[
  remember picture,
  overlay,
  >=latex
]
\draw[cyan,thick,->]
  (starta.south) --
  ++(0pt,-10pt) -| 
  (enda.south);
\draw[red!80!black,thick,->]
  (startb.south) --
  ++(0pt,-5pt) -| 
  (endb.south);
\draw[green!80!black,thick,->]
  (startc) to[out=60,in=90,looseness=1.4]
  (endc.north);
\end{tikzpicture}

\end{document}

(The border=20pt is necessary because standalone does not take the movement arrows into account.)

Compiling the main file with latexmk -pvc -xelatex test.tex --shell-escape gives something that looks correct. Progress! However, latexmk then enters an endless loop. Why is not exactly clear. Modifying .latexmkrc as detailed here does not seem to help, nor does calling latexmk with the -recorder- flag.

SEC
  • 761