27

Is there a way to use the TikZ libraries cd and external together?
This does not work:

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{cd, external}
\tikzexternalize

\begin{document}

\begin{tikzcd}
  A \arrow[rd] \arrow[r, "\varphi"] & B \\ & C
\end{tikzcd}

\end{document}

Error:

Runaway argument?

! File ended while scanning use of \tikzexternal@laTeX@collect@until@end@tikzpicture.

I’m using TikZ/PGF version 3.0.0 and tikz-cd version 0.9b.

  • I would say yes according to Compiling CircuiTikZ with -shell-escape (pdflatex), but I might be wrong. – Claudio Fiandrino Apr 17 '14 at 06:20
  • Thank you, but even after reading your linked answer, I don’t understand how to make it work. Replacing tikzcd with tikzpicture does not work. See Section 3.1 on page 11 of the tikz-cd manual (version 0.9b, of March 8, 2014). Or am I doing something wrong? Have you tried my example? Thanks! – Marco Varisco Apr 17 '14 at 13:15
  • Yes, I had a look, but honestly requires too much work and at the moment I'm lacking time. Basically, you can not replace tikzcd environment with tikzpicture because all definitions like \arrow are initialized when tikzcd begins. I would suggest you to contact the package author for this problem. – Claudio Fiandrino Apr 17 '14 at 14:14
  • Thank you, Claudio! That’s exactly what I thought. Oddly enough, I can’t find a contact information for the author, Florêncio Neves. – Marco Varisco Apr 17 '14 at 14:38
  • Perhaps you can leave him a message in chat with @profile-namesyntax. – Claudio Fiandrino Apr 17 '14 at 14:43
  • 1
    Your initial code does not compile for me. Where is the cd tikzlibrary defined? I thought tikz-cd was a separate package, not a TikZ library. – Andrew Stacey Apr 24 '14 at 07:05
  • @AndrewStacey tikz-cd is distributed separately, but the current version (v0.9b, which requires pgf v3.0.0) is implemented as a library, and tikz-cd.sty now only contains \usetikzlibrary{cd}. – Marco Varisco Apr 25 '14 at 02:19
  • 1
    @MarcoVarisco I see. I've just updated TL2013 on my machine so I'll take another look. – Andrew Stacey Apr 25 '14 at 06:57
  • Hello @MarcoVarisco , Please, if you are around, I really think that you should move your solution from the question to the answer, because the solution works just great and I am using it. The only thing is, when you're there, add [baseline=0pt] to the \begin{tikzpicture} so that the equation number gets placed correctly ;) – yo' Oct 22 '14 at 09:23
  • Hi, @tohecz, done! I actually already had a baseline adjustment in my answer. But [baseline=(current bounding box.west)] seems to work better for me. As for me, I am not using my own solution, which is Not A Good Idea according to @LoopSpace, but I am using the solution he provided. – Marco Varisco Oct 22 '14 at 21:31
  • @MarcoVarisco Upvoted! :) – yo' Oct 23 '14 at 10:02

4 Answers4

14

When I originally answered this, external was the only option available. Fortunately, that is no longer the case. Two new options for externalisation are now available from a CTAN mirror near you: memoize and robust-externalize.

Here's a solution using memoize, with tikzcd setup thanks to memoize's author.

\documentclass{article}
\usepackage{memoize}
\mmzset{%
  path={dir=memos},
  mkdir,
  % Sašo Živanović: https://chat.stackexchange.com/transcript/message/64689784#64689784
  auto={tikzcd}{memoize,verbatim},
}
\usepackage{tikz}
\usetikzlibrary{cd}

\begin{document}

\begin{tikzpicture} \node at (0,0) {node}; \end{tikzpicture}

\begin{tikzcd} A \arrow[rd] \arrow[r, "\varphi"] & B \ & C \end{tikzcd}

\end{document}

After a single compilation, the images are memoized as part of the production of the main PDF. At this point, they appear as extra pages, in addition to being produced in the appropriate location in the document.

results of single compilation

On the second compilation, the externs are extracted into separate PDF files and are now included in the main PDF, without requiring recompilation. The code for the images will be recompiled only if it changes or some further condition holds. (E.g. if you delete the PDFs or instruct memoize to do recompile or whatever.)

result of second compilation, using memoized images

Original answer

This solution does not allow you to externalise diagrams produced using the tikzcd environment but it does allow you to externalise other tikz pictures in your document. It is based on a workaround mentioned in the tikz manual in section 50.8.2 (pages 627-8). To make its use more convenient, etoolbox is used to patch the tikzcd environment. Essentially, this turns externalisation off at the beginning of tikzcd environments and then switches it back on again at their ends.

\documentclass{article}

\usepackage{etoolbox,tikz}

\usetikzlibrary{external} \tikzexternalize \usetikzlibrary{cd}

\AtBeginEnvironment{tikzcd}{\tikzexternaldisable} \AtEndEnvironment{tikzcd}{\tikzexternalenable}

\begin{document}

\begin{tikzpicture} \node at (0,0) {node}; \end{tikzpicture}

\begin{tikzcd} A \arrow[rd] \arrow[r, "\varphi"] & B \ & C \end{tikzcd}

\end{document}

On the first run, pdflatex --shell-escape <filename>.tex includes this output:

===== 'mode=convert with system call': Invoking 'pdflatex -shell-escape -halt-o
n-error -interaction=batchmode -jobname "<filename>-figure0" "\def\tikzexternalreal
job{<filename>}\input{<filename>}"' ========

<filename>-figure0.pdf looks like this:

Externalised picture

Subsequent runs include the output:

===== Image '<filename>-figure0' is up-to-date. ======

The PDF combines the externalised image from <filename>-figure0.pdf with the one produced from tikzcd on the fly:

One picture externalised; one not

How helpful this is will depend on what proportion of your pictures are tikzcd. If the answer is 100%, it will obviously be of no use at all. On the other hand, if the answer is less than 100%, there may be something to be said for it.

cfr
  • 198,882
12

The problem is the same as that in Problem with environment expansion and the Tikz external library. in that TeX does not see the \end{tikzpicture} inside the \end{tikzcd}. The solution in Problem with environment expansion and the Tikz external library. is to pack everything inside a macro to ensure that the customised end-of-environment is expanded before TeX starts gobbling so that the hidden \end{tikzpicture} is revealed. The adaptation of that in the question above is not the same because it adds an extra \end{tikzpicture} instead of unpacking the hidden one, and this leads to nesting of TikZ pictures which is Not A Good Idea.

(Nonetheless, just because something is not a good idea doesn't mean that it isn't the best idea, just that it should be used with extreme caution.)

If all your pictures are tikzcd environments then it seems that the right solution might be to tell TeX to look for tikzcd instead of tikzpicture. This is a reasonable thing to try to do because the first thing that \begin{tikzcd} does is to start a tikzpicture and the last thing that \end{tikzcd} does is to end it. However, my experiments at trying to change all tikzpictures in the externalisation code to tikzcd didn't work so I'm abandoning this for the time being (what would be nice would be an adaptation of the externalisation library that worked for any environment, not just tikzpictures).

Here's an adaptation of your adaptation of my answer to the linked question which instead of wrapping the tikzcd environment in a tikzpicture simply exposes the inner tikzpicture. Well, except that it doesn't since the inner tikzpicture is written as \tikzpicture ... \endtikzpicture which wouldn't match so we have to redefine the tikzcd environment to make the \tikzpicture and \endtikzpicture into \begin{tikzpicture} and \end{tikzpicture}. Elegant, it ain't, but it does avoid the nesting issue.

\documentclass{article}
%\url{https://tex.stackexchange.com/q/171931/86}
\usepackage{tikz}
\usepackage{environ}
\usetikzlibrary{cd,external}
\tikzexternalize

\makeatletter
\def\tikzcd@[#1]{%
  \begin{tikzpicture}[/tikz/commutative diagrams/.cd,every diagram,#1]%
  \ifx\arrow\tikzcd@arrow%
    \pgfutil@packageerror{tikz-cd}{Diagrams cannot be nested}{}
  \fi%
  \let\arrow\tikzcd@arrow%
  \let\ar\tikzcd@arrow%
  \def\rar{\tikzcd@xar{r}}%
  \def\lar{\tikzcd@xar{l}}%
  \def\dar{\tikzcd@xar{d}}%
  \def\uar{\tikzcd@xar{u}}%
  \def\urar{\tikzcd@xar{ur}}%
  \def\ular{\tikzcd@xar{ul}}%
  \def\drar{\tikzcd@xar{dr}}%
  \def\dlar{\tikzcd@xar{dl}}%
  \global\let\tikzcd@savedpaths\pgfutil@empty%
  \matrix[/tikz/matrix of \iftikzcd@mathmode math \fi nodes,
          /tikz/every cell/.append code={\tikzcdset{every cell}},
          /tikz/commutative diagrams/.cd,every matrix]%
  \bgroup}

\def\endtikzcd{%
  \pgfmatrixendrow\egroup%
  \pgfextra{\global\let\tikzcdmatrixname\tikzlastnode};%
  \tikzcdset{\the\pgfmatrixcurrentrow-row diagram/.try}%
  \begingroup%
    \tikzcd@enablequotes%
    \tikzcd@patcherrmsg%
    \tikzcd@savedpaths%
  \endgroup%
  \end{tikzpicture}%
  \ifnum0=`{}\fi}


\NewEnviron{mytikzcd}[1][]{%
  \def\@temp{\tikzcd@[#1]\BODY}%
  \expandafter\@temp\endtikzcd
}
\makeatother

\def\temp{&} \catcode`&=\active \let&=\temp
\begin{document}

\begin{mytikzcd}
  A \arrow{rd} \arrow{r}{\phi} & B \\ & C
\end{mytikzcd}

\begin{mytikzcd}
  A \arrow{rd} \arrow{r}{\phi} & B \\ & C
\end{mytikzcd}


\end{document}
Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751
  • Thank you. Unfortunately your code does not produce the right output for me. Here’s what I get. It probably has to do with the fact that we’re using different versions of tikz-cd.

    I do agree that what would be really nice is “an adaptation of the externalisation library that worked for any environment, not just tikzpictures.”

    – Marco Varisco Apr 25 '14 at 02:47
  • @MarcoVarisco One problem with testing externalisation is checking that the externalised graphics are from the current run, not the previous one. I think that's what happened here. I've fixed my hack, by making it even more hacky. It's definitely Not Elegant, but it fixes the problem you saw ... I think. – Andrew Stacey Apr 25 '14 at 07:09
  • Is there a way to overwrite the existing tikzcd environment instead of having to use one with a new name? I tried \RenewEnviron{tikzcd}… but I get the error File ended while scanning use of \tikzexternal@laTeX@collect@until@end@tikzpicture. Thanks! – Guillaume Brunerie Jun 19 '16 at 18:40
  • 2
    I experience a problem running this code: ! Package tikz Error: Sorry, the system call 'pdflatex -shell-escape -halt-on-error -interaction=batchmode -jobname "foo-figure0" "\def\tikzexternalrealjob{fo o}\input{foo}"' did NOT result in a usable output file 'foo-figure0' (expected one of .pdf:.jpg:.jpeg:.png:). Please verify that you have enabled system calls . -- But I used 'pdflatex -shell-escape foo.tex'. Any hints? – user44400 Oct 31 '16 at 10:38
  • I get the exact same problem as user44400. – Damien L May 18 '20 at 14:39
  • I'm a bit worried by the global definition of \def\temp{&} \catcode\&=\active \let&=\temp`: is it possible to redefine it locally, to avoid disturbing other packages? I created my own question here https://tex.stackexchange.com/questions/632643/how-to-avoid-redefining-ampersand-globally – tobiasBora Feb 04 '22 at 09:37
8

Here’s one solution that works with the external library and that I hadn’t thought of before.
We can put the tikzcd environment inside a tizkpicture’s node:

\begin{tikzpicture}
  \node {\begin{tikzcd}
    A \arrow[rd] \arrow[r, "\varphi"] & B \\ & C
  \end{tikzcd}};
\end{tikzpicture}

To get spacing and alignment right, the following options seem to work:

\begin{tikzpicture}[baseline=(current bounding box.west)]
  \node[inner sep=0, outer sep=0] {\begin{tikzcd}
    A \arrow[rd] \arrow[r, "\varphi"] & B \\ & C
  \end{tikzcd}};
\end{tikzpicture}

So combining this with Andrew Stacey’s excellent answer to a related question, we get:

\documentclass{article}

\usepackage{tikz, environ}
\usetikzlibrary{cd, external}
\tikzexternalize

\def\temp{&} \catcode`&=\active \let&=\temp

\NewEnviron{mycd}[1][]{%
  \begin{tikzpicture}[baseline=(current bounding box.west)]
    \node[inner sep=0, outer sep=0] {\begin{tikzcd}[#1]
      \BODY
    \end{tikzcd}};
  \end{tikzpicture}}

\begin{document}

\begin{equation}
\begin{mycd}[row sep=huge]
  A \arrow[rd] \arrow[r, "\varphi"] & B \\ & C
\end{mycd}
\end{equation}

\end{document}

It still feels like a dirty hack to me. Is there a better solution?

  • Thank you! This works well for me. However, in your solution, \BODY is expanded to late, and so the figures are not rebuilt if the content is changed. (Change to \tikzset{external/up to date check=diff} in order to see that \BODY is not expanded yet in the .md5 files). – Thorsten Apr 04 '17 at 14:52
  • Since my adjusted code did not fit into the comment box I have added another answer – Thorsten Apr 04 '17 at 15:09
  • Is there a solution to avoid redefining & globally? I'm worried to disturb other packages, unfortunately I can't find any solution https://tex.stackexchange.com/questions/632643/how-to-avoid-redefining-ampersand-globally – tobiasBora Feb 04 '22 at 09:24
6

I had to adjust Marco Varisco's answer, in order to get the figure automatically rebuilt on change. Furthermore, I let the baseline of the artificial tikzpicture be on the height of the tikzcd's baseline.

\documentclass{article}

\usepackage{tikz, environ, etoolbox}
\usetikzlibrary{cd, external}
\tikzexternalize
% activate the following such that you can check the macro expansion in
% *-figure0.md5 manually
%\tikzset{external/up to date check=diff}


\def\temp{&} \catcode`&=\active \let&=\temp

\newcommand{\mytikzcdcontext}[2]{
  \begin{tikzpicture}[baseline=(maintikzcdnode.base)]
    \node (maintikzcdnode) [inner sep=0, outer sep=0] {\begin{tikzcd}[#2]
        #1
    \end{tikzcd}};
  \end{tikzpicture}}

\NewEnviron{mytikzcd}[1][]{%
% In the following, we need \BODY to expanded before \mytikzcdcontext
% such that the md5 function gets the tikzcd content with \BODY expanded.
% Howerver, expand it only once, because the \tikz-macros aren't
% defined at this point yet. The same thing holds for the arguments to
% the tikzcd-environment.
\def\myargs{#1}%
\edef\mydiagram{\noexpand\mytikzcdcontext{\expandonce\BODY}{\expandonce\myargs}}%
\mydiagram%
}

\begin{document}

\begin{equation}
\begin{mytikzcd}[row sep=huge,baseline = (B.base)]
  A \arrow[rd] \arrow[r, "\varphi"] & |[alias=B]| B \\ & C
\end{mytikzcd}
\hbox{sharing a baseline with } B
\end{equation}

\end{document}
Thorsten
  • 332
  • I started using this and it works great, but I have the feeling that whenever I add or remove a diagram, compilation takes a lot time, probably because all the later diagrams have to be recompiled since the numbering is off. Can this be avoided somehow? Or probably it's an issue with tikz-external which is independent of this tikz-cd hack? – user313032 Jul 14 '23 at 07:09
  • @user313032: this is an issue with externalize in general because the filenames essentially only consist of a name prefix and an integer counter. You can avoid it by giving each tikz figure an explicit name or by setting \tikzsetfigurename{..} regularly, e.g. to something different for on each new subsection. – Thorsten Jul 15 '23 at 11:51
  • ahh interesting! how can I give names to figures? – user313032 Jul 16 '23 at 00:03
  • ahh probably you mean the command tikzsetfigurename{...} you're mentioning. anyway, I also found \tikzsetnextfilename{...} and used that to add random names to all diagrams – user313032 Jul 16 '23 at 01:10