[Tex/LaTex] with tikzexternalize, how to name the images with the current name of the imported file

externalshell-escapetikz-external

I use to save different tikz images, tikzexternalize, I have no problem if I let tikz name the pictures or if I needed a name for the next image.

\documentclass{article}

\usepackage{tikz}

\usetikzlibrary{external}

\tikzexternalize[prefix=figures-tikz/]

\begin{document}

%\tikzsetnextfilename{circle}
\begin{tikzpicture}
  \draw (0,0) circle [radius=1];
\end{tikzpicture}
\end{document}

As I have many included file, I wish I could name the pictures with the file name included rather than the source file name.

I use the currfile package to retrieve the name of the imported file current and I use the following configuration:

\documentclass{article}

\usepackage{tikz}

\usepackage{currfile}
\usetikzlibrary{external}

\tikzexternalize[prefix=figures-tikz/]
\tikzsetfigurename{\currfilename-}

\begin{document}

\begin{tikzpicture}
  \draw (0,0) circle [radius=1];
\end{tikzpicture}
\end{document}

this configuration does not work on my PC (windows 10, miketx2.9, texworks 0.61, xelatex or pdflatex)

Of course I configured texworks like the picture (I have a problem with shell-escape or write18)

enter image description here

Best Answer

For this to work, the code for the picture must actually be in an external file. Otherwise, the file name is identical with the name of the main file, which won't work.

You can create such files on-the-fly so the code is in your main .tex file, by using filecontents environment before \documentclass.

\begin{filecontents}{myfig.tex}
\begin{tikzpicture}
  \draw (0,0) circle [radius=1];
\end{tikzpicture}
\end{filecontents}

However, I would find this annoying in work-flow and only use it for MWEs.

The next problem is that \currentfilename includes the .tex extension which is confusing, at best. \currentfilebase is better. But this needs to be expanded when the figure name is set.

\expandafter\tikzsetfigurename\expandafter{\currfilebase-}

However, if we just do this in the preamble, then the name will be set to the name of the main .tex document and won't work. So we need to do it inside the external file with the picture's code. For convenience, we can define a wrapper macro.

\newcommand*\setmyname{%\
  \expandafter\tikzsetfigurename\expandafter{\currfilebase-}%
}

Now we could use \setmyname before each picture in an external file. But it would be convenient not to have to do so.

If all of your TikZ pictures are in external files and never included inline, then you can add the setting to the tikzpicture environment using etoolbox.

\AtBeginEnvironment{tikzpicture}{\setmyname}

However, you must not do this if you define any TikZ pictures inline in the main file.

Complete code to draw a circle:

\begin{filecontents}{myfig.tex}
\begin{tikzpicture}
  \draw (0,0) circle [radius=1];
\end{tikzpicture}
\end{filecontents}

\documentclass{article}
\usepackage{tikz}
\usepackage{currfile}
\usepackage{etoolbox}
\usetikzlibrary{external}
\tikzexternalize[prefix=ffigurau/]
\newcommand*\setmyname{%\
  \expandafter\tikzsetfigurename\expandafter{\currfilebase-}%
}
\AtBeginEnvironment{tikzpicture}{\setmyname}
\begin{document}
\input{myfig}
\end{document}

EDIT

This version removes the requirement that all externalised images be in external files. However, externalised images in external files will be named according to file name only if they are wrapped in a document environment. Furthermore, each external file must include a \documentclass. These conditions are a result of the fact that the code limits the effect of the naming change by modifying standalone's modified document environment.

Since the code uses standalone, the external image files can also include a full preamble, if required, which is convenient for compiling them individually during development. However, a complete preamble is not required. For example, it doesn't matter if the preamble doesn't load tikz and just has \documentclass{article} before \begin{document}. (Obviously such a file won't compile on its own, but it is fine for inclusion in the main .tex file.)

This relies on the grouping effect illustrated on page 611 of the PGF/TikZ manual.

\begin{filecontents}{myfig.tex}
\documentclass[tikz]{standalone}% or article of something, if preferred
\begin{document}
\begin{tikzpicture}
  \filldraw [magenta] (0,0) circle [radius=1];
\end{tikzpicture}
\end{document}
\end{filecontents}
\begin{filecontents}{myfig2.tex}
\documentclass[tikz]{standalone}
\begin{document}
\begin{tikzpicture}
  \filldraw [blue] (0,0) circle (1);
\end{tikzpicture}
\end{document}
\end{filecontents}

\documentclass{article}
\usepackage{standalone}
\usepackage{tikz}
\usepackage{currfile}
\usepackage{etoolbox}
\usetikzlibrary{external}
\tikzexternalize[prefix=ffigurau/]
\tikzsetfigurename{figure-}
\newcommand*\setmyname{%
  \expandafter\tikzsetfigurename\expandafter{\currfilebase-}%
}
\makeatletter
\apptocmd{\sa@document}{\setmyname}{\typeout{Append to document: OK!}}{\typeout{Append to document: Oh, no!}}
\makeatother
\begin{document}
\begin{tikzpicture}
  \draw [green] (0,0) -- (1,0);
\end{tikzpicture}
\input{myfig}
\begin{tikzpicture}
  \draw [red] (0,0) -- (1,0);
\end{tikzpicture}
\input{myfig2}
\end{document}

This produces images in ffigurau/ as follows: figure-0, figure-1, myfig-0 and myfig2-0. That is, the two inline pictures get the figure- name and the two in external files are named accordingly.

Here's a check that it works fine if an external file contains multiple images.

\begin{filecontents}{myfig.tex}
\documentclass[tikz]{article}
\begin{document}
\begin{tikzpicture}
  \filldraw [magenta] (0,0) circle (1);
\end{tikzpicture}
\end{document}
\end{filecontents}
\begin{filecontents}{myfig2.tex}
\documentclass[tikz]{standalone}
\begin{document}
\begin{tikzpicture}
  \filldraw [inner color=magenta, outer color=blue] (0,0) circle (1);
\end{tikzpicture}
\begin{tikzpicture}
  \filldraw [inner color=blue, outer color=magenta] (0,0) circle (1);
\end{tikzpicture}
\end{document}
\end{filecontents}
\begin{filecontents}{myfig3.tex}
\documentclass[tikz]{standalone}
\begin{document}
\begin{tikzpicture}
  \filldraw [blue] (0,0) circle (1);
\end{tikzpicture}
\end{document}
\end{filecontents}

\documentclass{article}
\usepackage{standalone}
\usepackage{tikz}
\usepackage{currfile}
\usepackage{etoolbox}
\usetikzlibrary{external}
\tikzexternalize[prefix=ffigurau/]
\tikzsetfigurename{figure-}
\newcommand*\setmyname{%
  \expandafter\tikzsetfigurename\expandafter{\currfilebase-}%
}
\makeatletter
\apptocmd{\sa@document}{\setmyname}{\typeout{Append to document: OK!}}{\typeout{Append to document: Oh, no!}}
\makeatother
\begin{document}
\input{myfig}
\begin{tikzpicture}
  \draw [green] (0,0) -- (1,0);
\end{tikzpicture}
\input{myfig2}
\begin{tikzpicture}
  \draw [red] (0,0) -- (1,0);
\end{tikzpicture}
\input{myfig3}
\end{document}

This produces images in ffigurau/ as follows: figure-0, figure-1, myfig-0, myfig2-0, myfig2-1 and myfig3-0, as expected.

EDIT EDIT

This version uses expl3, as well as standalone, etoolbox and currfile. It is subject to the same conditions as the version under EDIT i.e. all externalised images in external files must include \documentclass and a document environment.

However, unlike the previous version, this one allows you to override the name of the next externalised image in the standard way.

In order to do this, it modifies one TikZ user command (\tikzsetnextfilename) and one internal TikZ macro (\tikzexternal@getnextfilename@resetglobals), as well as standalone's replacement for document. This means there are more ways for updates to break the code. The code produces warnings on the console and in the .log file alerting the user either to the fact that stuff has been modified or to the fact that it hasn't.

\begin{filecontents}{myfig.tex}
\documentclass[tikz]{article}
\begin{document}
\begin{tikzpicture}
  \filldraw [magenta] (0,0) circle (1);
\end{tikzpicture}
\end{document}
\end{filecontents}
\begin{filecontents}{myfig2.tex}
\documentclass[tikz]{standalone}
\begin{document}
\begin{tikzpicture}
  \filldraw [inner color=magenta, outer color=blue] (0,0) circle (1);
\end{tikzpicture}
\begin{tikzpicture}
  \filldraw [inner color=blue, outer color=magenta] (0,0) circle (1);
\end{tikzpicture}
\end{document}
\end{filecontents}
\begin{filecontents}{myfig3.tex}
\documentclass[tikz]{standalone}
\begin{document}
\begin{tikzpicture}
  \filldraw [blue] (0,0) circle (1);
\end{tikzpicture}
\end{document}
\end{filecontents}

\documentclass{article}
\usepackage{standalone}
\usepackage{tikz}
\usepackage{currfile}
\usepackage{etoolbox}
\usepackage{xparse}
\usetikzlibrary{external}
\tikzexternalize[prefix=ffigurau/]
\tikzsetfigurename{figure-}
\ExplSyntaxOn
\tl_new:N \g_enext_figurename_tl
\cs_new_protected_nopar:Nn \enext_settofilename:
{
  \enext_tikzsetfigurename:x { \currfilebase- }
}
\cs_new_protected_nopar:Nn \enext_tikzsetfigurename:n
{
  \tikzsetfigurename { #1 }
}
\cs_generate_variant:Nn \enext_tikzsetfigurename:n { x }
\cs_new_protected_nopar:Nn \enext_tikzsetnextfilename:n
{
  \tikzsetnextfilename { #1 }
}
\cs_generate_variant:Nn \enext_tikzsetnextfilename:n { V }
\cs_new_protected_nopar:Nn \enext_setfigurename:
{
  \enext_settofilename:
  \enext_tikzsetnextfilename:V \g_enext_figurename_tl
}
\msg_new:nnnn { enext } { append ~ failed }
{
  \msg_warning_text:n { enext } ~ :: ~ Append ~ to ~ #1 ~ failed ~ \msg_line_context: !
}
{
  This ~ is ~ probably ~ not ~ your ~ fault ~ unless ~ you ~ redefined ~ the ~ modified ~ functions. ~
  Either ~ fix ~ the ~ code ~ yourself ~ or ~ ask ~ for ~ help. ~
  Apologies ~ for ~ any ~ inconvenience. ~
  All ~ code ~ provided ~ as-is ~ for ~ use ~ at ~ your ~ own ~ risk! ~
  As ~ a ~ goodwill ~ gesture, ~ a ~ full ~ refund ~ will ~ be ~ provided ~ on ~ request, ~ less ~ a ~ small ~ fee ~ to ~ cover ~ administration.
}
\msg_new:nnn { enext } { append ~ succeeded }
{
  \msg_warning_text:n { enext } ~ :: ~ Modified ~ #1 ~ \msg_line_context: !
}
\makeatletter
\apptocmd \sa@document
{
  \enext_setfigurename:
}{
  \msg_warning:nnn { enext } { append ~ succeeded } { document ~ environment }
}{
  \msg_warning:nnn { enext } { append ~ failed } { document ~ environment }
}
\apptocmd \tikzsetnextfilename
{
  \tl_gset:Nn \g_enext_figurename_tl { #1 }
}{
  \msg_warning:nnn { enext } { append ~ succeeded } { \tikzsetnextfilename }
}{
  \msg_warning:nnn { enext } { append ~ failed } { \tikzsetnextfilename }
}
\apptocmd \tikzexternal@getnextfilename@resetglobals
{
  \tl_gclear:N \g_enext_figurename_tl
}{
  \msg_warning:nnn { enext } { append ~ succeeded } { \tikzexternal@getnextfilename@resetglobals }
}{
  \msg_warning:nnn { enext } { append ~ failed } { \tikzexternal@getnextfilename@resetglobals }
}
\makeatother
\ExplSyntaxOff
\begin{document}
\input{myfig}
\tikzsetnextfilename{bill}
\begin{tikzpicture}
  \draw [green] (0,0) -- (1,0) circle (1);
\end{tikzpicture}
\tikzsetnextfilename{flowerpotmen}
\input{myfig2}
\begin{tikzpicture}
  \draw [red] (0,0) -- (1,0);
\end{tikzpicture}
\tikzsetnextfilename{ben}
\input{myfig3}
\end{document}

This produces externalised images in ffigurau named ben, bill, figure-0, flowerpotmen, myfig-0 and myfig2-0. That is, the first inline image is named bill rather than figure-0 (so the second is figure-0 rather than figure-1) and the first of the two images from the second external file is named flowerpotmen rather than myfig2-0 (so the second is myfig2-0 rather than myfig2-1) and the final image from an external file is named ben rather than myfig3-0. I think this is what is expected here.

EDIT EDIT EDIT

Use this only if the standalone package must be avoided. If you can't use the standalone package, then an alternative way to create a wrapper around the included code is to define a suitable wrapper around \input.

For example, the following defines \extinput{} which is very like \input{} except that it sets included figures' names appropriately. In this case, you don't even need currfile.

The downside, of course, is that only files input using \extinput rather than \input will get special treatment and so only they will be named according to their files' names. Also, the current version of the command is very simple and does not attempt to check the file name in any way. So this should not be used unless the file can be input using only its base name, as opposed to a relative or absolute path including an enclosing directory.

\begin{filecontents}{myfig4.tex}
\begin{tikzpicture}
  \filldraw [green] (0,0) circle (1);
\end{tikzpicture}
\end{filecontents}
\begin{filecontents}{myfig5.tex}
\begin{tikzpicture}
  \filldraw [inner color=green, outer color=purple] (0,0) circle (1);
\end{tikzpicture}
\begin{tikzpicture}
  \filldraw [inner color=purple, outer color=green] (0,0) circle (1);
\end{tikzpicture}
\end{filecontents}
\begin{filecontents}{myfig6.tex}
\begin{tikzpicture}
  \filldraw [purple] (0,0) circle (1);
\end{tikzpicture}
\end{filecontents}

\documentclass{article}
\usepackage{tikz}
\usepackage{etoolbox}
\usepackage{xparse}
\usetikzlibrary{external}
\tikzexternalize[prefix=ffigurau/]
\tikzsetfigurename{figure-}
\ExplSyntaxOn
\tl_new:N \g_enext_figurename_tl
\cs_new_protected_nopar:Nn \enext_tikzsetfigurename:n
{
  \tikzsetfigurename { #1 }
}
\cs_new_protected_nopar:Nn \enext_tikzsetnextfilename:n
{
  \tikzsetnextfilename { #1 }
}
\cs_generate_variant:Nn \enext_tikzsetnextfilename:n { V }
\cs_new_protected_nopar:Nn \enext_setfigurename:n
{
  \enext_tikzsetfigurename:n { #1 }
  \enext_tikzsetnextfilename:V \g_enext_figurename_tl
}
\msg_new:nnnn { enext } { append ~ failed }
{
  \msg_warning_text:n { enext } ~ :: ~ Append ~ to ~ #1 ~ failed ~ \msg_line_context: !
}
{
  This ~ is ~ probably ~ not ~ your ~ fault ~ unless ~ you ~ redefined ~ the ~ modified ~ functions. ~
  Either ~ fix ~ the ~ code ~ yourself ~ or ~ ask ~ for ~ help. ~
  Apologies ~ for ~ any ~ inconvenience. ~
  All ~ code ~ provided ~ as-is ~ for ~ use ~ at ~ your ~ own ~ risk! ~
  As ~ a ~ goodwill ~ gesture, ~ a ~ full ~ refund ~ will ~ be ~ provided ~ on ~ request, ~ less ~ a ~ small ~ fee ~ to ~ cover ~ administration.
}
\msg_new:nnn { enext } { append ~ succeeded }
{
  \msg_warning_text:n { enext } ~ :: ~ Modified ~ #1 ~ \msg_line_context: !
}
\NewDocumentCommand \extinput { m }
{
  \group_begin:
    \enext_setfigurename:n { #1 - }
    \file_input:n { #1 }
  \group_end:
}
\makeatletter
\apptocmd \tikzsetnextfilename
{
  \tl_gset:Nn \g_enext_figurename_tl { #1 }
}{
  \msg_warning:nnn { enext } { append ~ succeeded } { \tikzsetnextfilename }
}{
  \msg_warning:nnn { enext } { append ~ failed } { \tikzsetnextfilename }
}
\apptocmd \tikzexternal@getnextfilename@resetglobals
{
  \tl_gclear:N \g_enext_figurename_tl
}{
  \msg_warning:nnn { enext } { append ~ succeeded } { \tikzexternal@getnextfilename@resetglobals }
}{
  \msg_warning:nnn { enext } { append ~ failed } { \tikzexternal@getnextfilename@resetglobals }
}
\makeatother
\ExplSyntaxOff
\begin{document}
\extinput{myfig4}
\begin{tikzpicture}
  \draw [green] (0,0) -- (1,0) circle (1);
\end{tikzpicture}
\tikzsetnextfilename{circ}
\extinput{myfig5}
\begin{tikzpicture}
  \draw [red] (0,0) -- (1,0);
\end{tikzpicture}
\extinput{myfig6}
\end{document}