1

I'm using tikz external to get plots rendered faster. But legends in the plots are missing if external is used.

Update: I'm using label to create legend. The same issue does not occur if \legend is used. I wanted to use label as it gives me more flexibility in groupplot. See question PGFplots - single legend in a group plot

main file

\documentclass{article}
\usepackage{tikz}
\usepackage{pgfplots}
\usetikzlibrary{pgfplots.groupplots,external}
\tikzexternalize[prefix=tikz/]
\tikzset{external/force remake}
\begin{document}
\begin{figure}
    \input{sample.tikz}         % input TiKZ figure code
    \caption{legends missing in tikz external}  % caption
    \label{fig:sample}              % label
\end{figure}
\end{document}

sample.tikz

% Style to select only points from #1 to #2 (inclusive)
\pgfplotsset
{
    select coords between index/.style 2 args=
    {
        x filter/.code=
        {
                \ifnum\coordindex<#1\def\pgfmathresult{}\fi
                \ifnum\coordindex>#2\def\pgfmathresult{}\fi
        }
    }
}
\begin{tikzpicture}
    \begin{groupplot}[group style={group size= 2 by 1},height=4cm,width=4cm,max space between ticks=20,minor tick num=1,tick label style={font=\footnotesize}]
        \nextgroupplot[title={2K},xtick=data]
                \addplot[blue,dotted,mark=asterisk]         [select coords between index={0}{4}] table[x=threads,y=foo-MOPS,col sep=space]{Data/mic/threadSweep.csv};       \label{plots:foo:cka}
                \addplot[green, dotted,mark=o]              [select coords between index={0}{4}] table[x=threads,y=bar-MOPS,col sep=space]{Data/mic/threadSweep.csv};       \label{plots:bar:cka}
                \coordinate (top) at (rel axis cs:0,1);% coordinate at top of the first plot
        \nextgroupplot[title={20K} ,xtick=data] 
                \addplot[blue,dotted,mark=asterisk]         [select coords between index={5}{9}] table[x=threads,y=foo-MOPS,col sep=space]{Data/mic/threadSweep.csv};  
                \addplot[green, dotted,mark=o]              [select coords between index={5}{9}] table[x=threads,y=bar-MOPS,col sep=space]{Data/mic/threadSweep.csv}; 
                \coordinate (bot) at (rel axis cs:1,0);% coordinate at bottom of the last plot
\end{groupplot}
    \path (top-|current bounding box.west)-- node[anchor=south,rotate=90] {\small system throughput} (bot-|current bounding box.west);
    \path (top|-current bounding box.north)-- coordinate(legendpos) (bot|-current bounding box.north);
    \matrix[matrix of nodes, anchor=south, draw, inner sep=0.2em, draw] at ([yshift=1ex]legendpos)
    {
        \ref{plots:foo:cka}&    foo         & [5pt]
        \ref{plots:bar:cka}&    bar         \\
    };
\end{tikzpicture}

output enter image description here

arunmoezhi
  • 1,270

3 Answers3

2

You can use a patch suggested by Ijon Tichy on TeXwelt (German) to externalize a tikzpicture using \label and \ref for the legend.

The following example is from PGFplots - single legend in a group plot because I do not have the threadSweep.csv.

\documentclass[margin=5mm]{standalone}
\usepackage{pgfplots}
\usetikzlibrary{matrix}
\usepgfplotslibrary{groupplots}
\pgfplotsset{compat=newest}

\usepgfplotslibrary{external}
\tikzexternalize
\tikzsetexternalprefix{external_figs/}
\tikzset{external/up to date check=md5}% < added

%%% %http://texwelt.de/wissen/fragen/9476/labels-an-pgfplots/9527 (by Ijon Tichy)
\usepackage{scrlfile}
\usepackage{etoolbox}
\makeatletter
\newif\if@lateexternal
\newcommand*{\nextwithlateexternal}{\@lateexternaltrue}
\renewcommand*{\@lateexternalfalse}{\global\let\if@lateexternal\iffalse}
% Den Systemaufruf von external so ändern, dass er optional doppelt
% stattfindet: Zunächst wie gehabt unmittelbar und zusätzlich nachdem
% die aux-Datei geschlossen (und sogar neu gelesen) wurde.
\patchcmd\tikzexternal@externalizefig@systemcall@@
  {\immediate\write18{\pgf@tempa}}%
  {\immediate\write18{\pgf@tempa}%
    \if@lateexternal
      \begingroup
        \protected@edef\reserved@a{%
          \noexpand\endgroup
          \noexpand\AfterReadingMainAux{%
            \noexpand\immediate\noexpand\write18{%
              \expandafter\detokenize\expandafter{\pgf@tempa}}%
          }%
        }%
      \reserved@a
    \fi
  }%
  {}%
  {\patchFailedError}
% Nun dafür sorgen, dass der Aufruf \nextwithlateexternal nur auf
% den nächsten potentiellen Systemaufruf von external wirkt statt
% auf den nächsten tatsächlichen oder gar alle:
\apptocmd\tikzexternal@externalizefig@systemcall@@
  {\@lateexternalfalse}
  {}
  {\patchFailedError}
\makeatother
%%%


\begin{document}

\nextwithlateexternal% < added
\begin{tikzpicture}
    \begin{groupplot}[group style={group size= 2 by 4},height=5cm,width=6.4cm]
        \nextgroupplot[title=type1,ylabel={Range1 }]
                \addplot[blue] {x};\label{plots:plot1}
                \addplot[red] {x^2};\label{plots:plot2}
                \addplot[green] {2*x};\label{plots:plot3}
                \coordinate (top) at (rel axis cs:0,1);% coordinate at top of the first plot
        \nextgroupplot[title=type2]
                \addplot[blue]{x};
        \nextgroupplot[ylabel={Range2 }]
                \addplot[blue]{x};
        \nextgroupplot
                \addplot[blue]{x};
        \nextgroupplot[ylabel={Range3 }]
                \addplot[blue]{x};
        \nextgroupplot
                \addplot[blue]{x};
        \nextgroupplot[xlabel={Number of Threads},ylabel={Range4 }]
                \addplot[blue]{x};
        \nextgroupplot[xlabel={Number of Threads}]
                \addplot[blue]{x};
                \coordinate (bot) at (rel axis cs:1,0);% coordinate at bottom of the last plot
    \end{groupplot}
    \path (top-|current bounding box.west)-- 
          node[anchor=south,rotate=90] {throughput} 
          (bot-|current bounding box.west);
% legend
\path (top|-current bounding box.north)--
      coordinate(legendpos)
      (bot|-current bounding box.north);
\matrix[
    matrix of nodes,
    anchor=south,
    draw,
    inner sep=0.2em,
    draw
  ]at([yshift=1ex]legendpos)
  {
    \ref{plots:plot1}& curve 1&[5pt]
    \ref{plots:plot2}& curve2&[5pt]
    \ref{plots:plot3}& curve 3\\};
\end{tikzpicture}
\end{document}

Run twice.

Note: AFAIK this does not work with latexmk. And \tikzset{external/force remake} could only be used in the first run.

esdd
  • 85,675
  • I can't compile this code, I get the following error: ! Undefined control sequence. <argument> \patchFailedError. What am I missing? – aaragon May 10 '21 at 18:57
  • The code in the answer patches an internal command. This internal command has been changed or was removed in newer pgfplots versions. Therefore the patch now fails. But the original issue has been fixed for a long time. So the patch is not needed anymore. – esdd May 12 '21 at 09:15
1

You need to read the notes on use of \label and \ref in the pgfplots manual's section on externalisation. (Section 7.1 in my copy.)

You cannot use the default mode if you wish to combine these with externalisation. You need to use one of the available alternatives.

From page 473 of the pgfplots manual:

For point a), a \ref inside of an externalized graphics works only if you issue the required system call manually or by make. The initial configuration mode=convert with system call does not support \ref. But you can copy–paste the system call generated by mode=convert with system call and issue it manually. The reason is that \ref information is stored in the main .aux file – but this auxiliary file is not completely written when mode=convert with system call is invoked (there is a race condition). Note that \pageref is not supported (sorry). Thus: if you have \ref inside of external graphics, consider using mode=list and make or copy–paste the system call for the image(s) and issue it manually.

cfr
  • 198,882
  • how do i specify mode=list. I tried \tikzset{external/mode list} and \tikzset{external/mode=list}. They threw errors. Then I tried \tikzexternalize[mode=list and make]. But the legend is still missing – arunmoezhi Dec 13 '15 at 04:58
  • Page 473: /tikz/external/mode=list and make}. So \tikzset{/tikz/external/mode=list and make} should be what you want. – cfr Dec 13 '15 at 14:19
1

In the meantime (with the external library of PGFPlots v1.14) it is sufficient to compile the following MWE twice to get the desired result ...

(I just deleted the middle two rows of plots in the MWE to save some space.)

\documentclass{article}
\usepackage{pgfplots}
    \usetikzlibrary{
        matrix,
        pgfplots.external,
        pgfplots.groupplots,
    }
    \pgfplotsset{compat=1.3}
    \tikzexternalize
\begin{document}
    \begin{tikzpicture}
        \begin{groupplot}[
            group style={
                group size= 2 by 2,
            },
            height=5cm,
            width=6.4cm,
        ]
        \nextgroupplot[title=type1,ylabel={Range1 }]
            \addplot[blue] {x};\label{plots:plot1}
            \addplot[red] {x^2};\label{plots:plot2}
            \addplot[green] {2*x};\label{plots:plot3}
            \coordinate (top) at (rel axis cs:0,1);% coordinate at top of the first plot
        \nextgroupplot[title=type2]
            \addplot[blue]{x};
        \nextgroupplot[xlabel={Number of Threads},ylabel={Range4 }]
            \addplot[blue]{x};
        \nextgroupplot[xlabel={Number of Threads}]
            \addplot[blue]{x};
            \coordinate (bot) at (rel axis cs:1,0);% coordinate at bottom of the last plot
        \end{groupplot}
        \path (top-|current bounding box.west)--
              node[anchor=south,rotate=90] {throughput}
              (bot-|current bounding box.west);
        % legend
        \path (top|-current bounding box.north)--
              coordinate(legendpos)
              (bot|-current bounding box.north);
        \matrix[
            matrix of nodes,
            anchor=south,
            draw,
            inner sep=0.2em,
        ] at ([yshift=1ex]legendpos) {
            \ref{plots:plot1}& curve 1&[5pt]
            \ref{plots:plot2}& curve2&[5pt]
            \ref{plots:plot3}& curve 3\\
        };
    \end{tikzpicture}
\end{document}

image showing the result of above code without whitespace

Stefan Pinnow
  • 29,535
  • I used part of this answer to answer a question which (currently) has an open bounty. Since it originally is your code, if you would like to add an answer there too, I could delete mine. – Max Aug 23 '18 at 21:28
  • @Max, it is perfectly fine for me if you get the bounty :) – Stefan Pinnow Aug 24 '18 at 04:31
  • I can't compile this code, I get the following error: ! Undefined control sequence. <argument> \patchFailedError. What am I missing? – aaragon May 10 '21 at 13:47
  • @aaragon, sorry, but with an up-to-date MiKTeX I can still compile the code without any problems ... – Stefan Pinnow May 10 '21 at 15:15
  • @StefanPinnow I meant to put this comment in the accepted answer. Does that one compile for you? – aaragon May 10 '21 at 15:42