[Tex/LaTex] Tikz externalize + macro’s ? Or how would you do it

external filesmacrostikz-externaltikz-pgf

I'm currently building courses for some of my college teachers. The aim is to make their Word/MS paint generated study material into high grade Latex/Tikz material.

I'm aiming for high consistency in figures (expecially plots) and I've therefore written a bunch of macro's that generate tikz pictures for me from data I get form Matlab using the matlab2tikz.m code (which was written at my college!).

What I've done is write a bunch of "set" macro's that set a current variable to be something e.g. \figxlabel{$x$-axis} (see my code below) would set another macro \thefigxlabelto contain $x$-axis. All of this info is then used in the next \matlabfig{data.tikz}, after which it is reset to neutral values. Since everything is in macro's I don't need to remember what the special settings for a plot were, and even better I can change the \matlabfig macro to change all pictures at once!

But, then I discovered the Tikz externalize library which, since my documents are getting large (I split them up but still…), would come in very handy. Yet, this library doesn't support macro usage in tikzpictures.. What would you do to combine the best of two worlds?

I was thinking to use the macro's to set global tikz properties that apply to all figures (I don't know if i can do \tikzset{every xlabel={$x$-axis}}) and then change this between pictures, so it would only apply to the next one. This way there wouldn't be any macro's inside the tikz environment. I'd have to use a non-macro \begin and \end structure then I'm guessing.

All my written macro's are given in the next piece of code (these should work):

\def\figurewidth{7.5cm}
\def\figureheight{4cm}

\newcommand{\thefigtitle}[0]{\null}%title
\newcommand{\thefigtith}[0]{0cm}
\newcommand{\figtitle}[1]{%
    \renewcommand{\thefigtitle}{#1}%
    \renewcommand{\thefigtith}{0.35cm}%
}

\def\figysep{1}

\newcommand{\thefigxlabel}[0]{\null}%xlabel
\newcommand{\thefigxh}[0]{0}

\newcommand{\figxlabel}[1]{%
    \renewcommand{\thefigxlabel}{#1}%
    \renewcommand{\thefigxh}{-0.35}%
}

\newcommand{\thefigylabel}{}%ylabel
\newcommand{\figylabel}[1]{\renewcommand{\thefigylabel}{#1}}

\newcommand{\thefiglabel}{ }%label
\newcommand{\figlabel}[1]{%
    \renewcommand{\thefiglabel}{\label{#1}}%
}  

\newcommand{\thefigxrange}[0]{ }%x-range
\newcommand{\figxrange}[2]{
    \renewcommand{\thefigxrange}{xmin=#1,xmax=#2,}
}  


\newcommand{\thefigyrange}[0]{ }%y-range
\newcommand{\figyrange}[2]{%
    \renewcommand{\thefigyrange}{ymin=#1,ymax=#2,}
}  

\newcommand{\thefiglegend}{ }%legend
\newcommand{\figlegend}[2]{%
    \renewcommand{\thefiglegend}{legend entries={#1},legend pos={#2}}%
}

\newcommand{\thefigcaption}{\caption{}}%caption
\newcommand{\figcaption}[1]{\renewcommand{\thefigcaption}[0]{\caption{#1}}}

\newcommand{\thefigextra}{ }%extra options
\newcommand{\figextra}[1]{\renewcommand{\thefigextra}[0]{#1}}

\newcommand{\thefigpos}{htp}
\newcommand{\figpos}[1]{\renewcommand{\thefigpos}[0]{#1}}

\newcommand{\matlabfig}[1]{%figuur zelf
    \begin{figure}[\thefigpos]
        \centering
        \begin{tikzpicture}[]
            \draw[use as bounding box,draw=none](0,0+\thefigxh)rectangle(\figurewidth,\figureheight+\thefigtith);           
            \begin{axis}[%
            view={0}{90},
            scale only axis,
            width=\figurewidth,
            height=\figureheight,
            y tick label style={font={\tiny}}, 
            x tick label style={font={\tiny}}, 
            title={\textbf{\textsc{\thefigtitle}}},
            xlabel={\textbf{\thefigxlabel}},
            ylabel={\textbf{\thefigylabel}},
            \thefigxrange
            \thefigyrange
            axis on top,\thefiglegend,
            \thefigextra,
            minor tick num=1
            ]
                \input{#1}
            \end{axis}
        \end{tikzpicture}
    \thefigcaption\thefiglabel
\end{figure}
%alles resetten 
\figreset}


\newcommand{\figreset}[0]{
\renewcommand{\thefigtitle}{ }%
\renewcommand{\thefigxlabel}{ }% 
\renewcommand{\thefigxh}{0}%
\renewcommand{\thefigtith}{0cm}% 
\renewcommand{\thefigylabel}{}% 
\renewcommand{\thefigcaption}{\caption{}}%
\renewcommand{\thefiglabel}{}%
\renewcommand{\thefiglegend}{}%
\renewcommand{\thefigxrange}{}%
\renewcommand{\thefigyrange}{}%
\renewcommand{\thefigextra}{ }%
\renewcommand{\thefigpos}{htp}%
}

EDIT: added a working minimal example

contents of example.tex:

\documentclass{article}

\usepackage{tikz}
\usepackage{pgfplots}

\def\figurewidth{7.5cm}
\def\figureheight{4cm}

\newcommand{\thefigtitle}[0]{\null}%title
\newcommand{\thefigtith}[0]{0cm}
\newcommand{\figtitle}[1]{%
    \renewcommand{\thefigtitle}{#1}%
    \renewcommand{\thefigtith}{0.45cm}%
}

\def\figysep{1}

\newcommand{\thefigxlabel}[0]{\null}%xlabel
\newcommand{\thefigxh}[0]{0}

\newcommand{\figxlabel}[1]{%
    \renewcommand{\thefigxlabel}{#1}%
    \renewcommand{\thefigxh}{-0.35}%
}

\newcommand{\thefigylabel}{}%ylabel
\newcommand{\figylabel}[1]{\renewcommand{\thefigylabel}{#1}}

\newcommand{\thefiglabel}{ }%label
\newcommand{\figlabel}[1]{%
    \renewcommand{\thefiglabel}{\label{#1}}%
}  

\newcommand{\thefigxrange}[0]{ }%x-range
\newcommand{\figxrange}[2]{
    \renewcommand{\thefigxrange}{xmin=#1,xmax=#2,}
}  


\newcommand{\thefigyrange}[0]{ }%y-range
\newcommand{\figyrange}[2]{%
    \renewcommand{\thefigyrange}{ymin=#1,ymax=#2,}
}  

\newcommand{\thefiglegend}{ }%legend
\newcommand{\figlegend}[2]{%
    \renewcommand{\thefiglegend}{legend entries={#1},legend pos={#2}}%
}

\newcommand{\thefigcaption}{\caption{}}%caption
\newcommand{\figcaption}[1]{\renewcommand{\thefigcaption}[0]{\caption{#1}}}

\newcommand{\thefigextra}{ }%extra options
\newcommand{\figextra}[1]{\renewcommand{\thefigextra}[0]{#1}}

\newcommand{\thefigpos}{htp}
\newcommand{\figpos}[1]{\renewcommand{\thefigpos}[0]{#1}}

\newcommand{\matlabfig}[1]{%figuur zelf
    \begin{figure}[\thefigpos]
        \centering
        \begin{tikzpicture}[]
            \draw[use as bounding box,draw=none](0,0+\thefigxh)rectangle(\figurewidth,\figureheight+\thefigtith);            
            \begin{axis}[%
            view={0}{90},
            scale only axis,
            width=\figurewidth,
            height=\figureheight,
            y tick label style={font={\tiny}}, 
            x tick label style={font={\tiny}}, 
            title={\textbf{\textsc{\thefigtitle}}},
            xlabel={\textbf{\thefigxlabel}},
            ylabel={\textbf{\thefigylabel}},
            \thefigxrange
            \thefigyrange
            axis on top,\thefiglegend,
            \thefigextra,
            minor tick num=1
            ]
                \input{#1}
            \end{axis}
        \end{tikzpicture}
    \thefigcaption\thefiglabel
\end{figure}
%alles resetten 
\figreset}


\newcommand{\figreset}[0]{
\renewcommand{\thefigtitle}{ }%
\renewcommand{\thefigxlabel}{ }% 
\renewcommand{\thefigxh}{0}%
\renewcommand{\thefigtith}{0cm}% 
\renewcommand{\thefigylabel}{}% 
\renewcommand{\thefigcaption}{\caption{}}%
\renewcommand{\thefiglabel}{}%
\renewcommand{\thefiglegend}{}%
\renewcommand{\thefigxrange}{}%
\renewcommand{\thefigyrange}{}%
\renewcommand{\thefigextra}{ }%
\renewcommand{\thefigpos}{htp}%
}


\pgfplotsset{
    every tick/.style={thin,color=black},
    every axis legend/.append style={nodes={right}},
    every axis legend/.append style={font=\tiny},
    compat=newest
}

\tikzstyle{every node}=[font=\scriptsize]

\begin{document}

 %%
\figcaption{an example fig}
\figlabel{fig:example}
\figtitle{example}
\figxlabel{$x$ axis}
\figylabel{$y$ axis}
\figlegend{data.tikz}{north west}
\figxrange{0}{6}
\figyrange{0}{5}
\figextra{}
\matlabfig{data.tikz}
%%

%another fig, but as you can see all was resetted
\matlabfig{data.tikz} 


\end{document}

contents of data.tikz:

\addplot [
color=black,
solid
]
coordinates{(0,0) (3,3) (4,5) (5,4)};

How to to it with environments?

The following example contains the most basic newenvironment to create a tikzpicture. What I'm actually going for has a bit more options, but the problem remains the same.

Here's a small piece of sample code:

\documentclass[a4paper]{article}

\usepackage{tikz}
\usetikzlibrary{external}

\newenvironment{mytikz}{%
\begin{figure}[htp]
\centering
\begin{tikzpicture}}
{\end{tikzpicture}
\caption{}
\end{figure}}

\tikzexternalize

\begin{document}

\begin{mytikz}
    \draw(0,0) circle (1cm);
\end{mytikz}

\end{document}

Best Answer

The MWE works just fine using the externalisation library. Converting the syntax to use pgfkeys actually solves the scoping problem since keys are local to the current scope. Thus you can pass the keys and values as an option to the matlabfig command and they will be used only internally to that command.

I'm only a newcomer to the world of of pgfkeys so there may be better ways of implementing this, but here's a conversion of your macros into pgfkeys language. I only changed one thing in the input syntax: the ranges are specified as x range=0:6 instead of x range={0}{6}.

When putting this in to an environment, the problem is that the externalisation library looks for \end{tikzpicture} without doing any expansion. In an ordinary environment, this doesn't work because the \end{tikzpicture} is hidden behind a \end{myenvironment}. So we have to have a method whereby the \end{myenvironment} is expanded at the same time as the \begin{tikzpicture}. Fortunately such a method exists and is to use the environ package. This makes environments behave a little like commands.

\documentclass{article}
\usepackage{environ}
\usepackage{tikz}
\usetikzlibrary{external}
\tikzexternalize
\tikzset{external/force remake=true}
\usepackage{pgfplots}

\def\figurewidth{7.5cm}
\def\figureheight{4cm}

\pgfkeys{/matlab plot/.cd,
  title/.default={\null},
  title height/.default={0cm},
  x label/.default={\null},
  x label height/.default={0},
  x range/.code args={#1:#2}{\pgfkeyssetvalue{/matlab plot/x range}{xmin=#1,xmax=#2}},
  y range/.code args={#1:#2}{\pgfkeyssetvalue{/matlab plot/y range}{ymin=#1,ymax=#2}},
  legend/.code args={#1#2}{\pgfkeyssetvalue{/matlab plot/legend}{legend entries=#1,legend pos=#2}},
  title/.code={%
    \pgfkeyssetvalue{/matlab plot/title}{#1}%
    \pgfkeyssetvalue{/matlab plot/title height}{0.45cm}},
  x label/.code={%
    \pgfkeyssetvalue{/matlab plot/x label}{#1}%
    \pgfkeyssetvalue{/matlab plot/x label height}{-0.35}},
  caption/.code={%
    \pgfkeyssetvalue{/matlab plot/caption}{\caption{#1}}},
  label/.code={\pgfkeyssetvalue{/matlab plot/caption}{\label{#1}}}%
}

% Initialise
\pgfkeyssetvalue{/matlab plot/caption}{}
\pgfkeyssetvalue{/matlab plot/label}{}
\pgfkeyssetvalue{/matlab plot/x label}{}
\pgfkeyssetvalue{/matlab plot/y label}{}
\pgfkeyssetvalue{/matlab plot/extra}{}
\pgfkeyssetvalue{/matlab plot/legend}{}
\pgfkeyssetvalue{/matlab plot/x range}{}
\pgfkeyssetvalue{/matlab plot/x range}{}
\pgfkeyssetvalue{/matlab plot/y range}{}
\pgfkeyssetvalue{/matlab plot/y range}{}
\pgfkeyssetvalue{/matlab plot/pos}{}

\def\figysep{1}

\newcommand{\matlabfig}[2][]{%figuur zelf
  \begingroup
  \pgfkeys{/matlab plot/.cd,#1}
    \begin{figure}[\pgfkeysvalueof{/matlab plot/pos}]
        \centering
        \begin{tikzpicture}[]
%            \draw[use as bounding box,draw=none](0,0+\pgfkeysvalueof{x label height})rectangle(\figurewidth,\figureheight+\pgfkeysvalueof{title height});           
            \begin{axis}[%
            view={0}{90},
            scale only axis,
            width=\figurewidth,
            height=\figureheight,
            y tick label style={font={\tiny}}, 
            x tick label style={font={\tiny}}, 
            title={\textbf{\textsc{\pgfkeysvalueof{/matlab plot/title}}}},
            xlabel={\textbf{\pgfkeysvalueof{/matlab plot/x label}}},
            ylabel={\textbf{\pgfkeysvalueof{/matlab plot/y label}}},
            \pgfkeysvalueof{/matlab plot/x range},
            \pgfkeysvalueof{/matlab plot/y range},
            axis on top,
            \pgfkeysvalueof{/matlab plot/legend},
            \pgfkeysvalueof{/matlab plot/extra}
            minor tick num=1
            ]
                \input{#2}
            \end{axis}
        \end{tikzpicture}
        \pgfkeysvalueof{/matlab plot/caption}
        \pgfkeysvalueof{/matlab plot/label}
\end{figure}
    \endgroup}

\NewEnviron{MatlabFig}[1][]{%
  \begingroup
  \pgfkeys{/matlab plot/.cd,#1}
  \begin{figure}[\pgfkeysvalueof{/matlab plot/pos}]
  \centering
  \begin{tikzpicture}
%            \draw[use as bounding box,draw=none](0,0+\pgfkeysvalueof{x label height})rectangle(\figurewidth,\figureheight+\pgfkeysvalueof{title height});           
            \begin{axis}[%
            view={0}{90},
            scale only axis,
            width=\figurewidth,
            height=\figureheight,
            y tick label style={font={\tiny}}, 
            x tick label style={font={\tiny}}, 
            title={\textbf{\textsc{\pgfkeysvalueof{/matlab plot/title}}}},
            xlabel={\textbf{\pgfkeysvalueof{/matlab plot/x label}}},
            ylabel={\textbf{\pgfkeysvalueof{/matlab plot/y label}}},
            \pgfkeysvalueof{/matlab plot/x range},
            \pgfkeysvalueof{/matlab plot/y range},
            axis on top,
            \pgfkeysvalueof{/matlab plot/legend},
            \pgfkeysvalueof{/matlab plot/extra}
            minor tick num=1
            ]
  \BODY
            \end{axis}
        \end{tikzpicture}
        \pgfkeysvalueof{/matlab plot/caption}
        \pgfkeysvalueof{/matlab plot/label}
\end{figure}
    \endgroup}

\pgfplotsset{
    every tick/.style={thin,color=black},
    every axis legend/.append style={nodes={right}},
    every axis legend/.append style={font=\tiny},
    compat=newest
}

\tikzstyle{every node}=[font=\scriptsize]

\begin{document}

 %%
\matlabfig[
        caption=an example fig,
        label=fig:example,
        title=example,
        x label=$x$ axis,
        y label=$y$ axis,
        legend={data.tikz}{north west},
        x range=0:6,
        y range=0:5,
        ]{tikzextkeys_data.tikz}

%%

%another fig, but as you can see all was resetted
\matlabfig{tikzextkeys_data.tikz} 

\begin{MatlabFig}[
        caption=an example fig,
        label=fig:example,
        title=example,
        x label=$x$ axis,
        y label=$y$ axis,
        legend={data.tikz}{north west},
        x range=0:6,
        y range=0:5,
        ]
\input{tikzextkeys_data.tikz}
\end{MatlabFig}

\end{document}

(NB In this code, something is going wrong when I uncomment the bounding box line. It's to do with getting the lengths and values of the keys to interact properly.)