# create a standalone legend for a grid of plots

This question somewhat relates to Using a pgfplots-style legend in a plain-old tikzpicture

I'm looking for a better way to do the following. I'm generating a huge amount of data, and from the data I'm programmatically generating files which I include in my latex source file. I'm generating a grid of plots using multiple invocations of the following pattern.

\begin{tikzpicture}
\begin{axis}[...]
\end{axis}
\end{tikzpicture}


And I'm forming a grid of these plots using

\begin{tabular}{ccc}
...
\end{tabular}


This works sort of well, except for problems with the legend. I don't want a legend on all the plots, because (1) they take up to much space on the page, (2) most of the legends have the same information, and (3) latex does not them out in a beautiful way as they don't all have the same bbox. And I don't want just one single legend because (4) it latex lays the page out unevenly and (5) not all the legends contain exactly the same information.

Here is an image of what it looks like when all the plots have legends.

Here is an image of what it looks like with one single legend.

QUESTION 1: Is there a way to tell tikz that I want a uniform grid of plots and a single uniquified legend?

QUESTION 2: Is there a way generate a standalone legend based on a set of plots, even when tikz has chosen the colors and mark types automatically? (this is of course related to but not exactly the same as Using a pgfplots-style legend in a plain-old tikzpicture)

QUESTION 3: Since I'm generating the plots programmatically, I can generate the marks and colors myself, and have the data for a legend, which I can generate myself as described in the post referenced above. Is this the best approach?

I have attempted to create a workable example, much reduced from the amount of data I normally work with. Also, in my real case I have separate files for each \begin{tikzpicture}...\end{tikzpicture} which I include with \input{filename.ltxdat}. I don't believe these differences matter, except to emphasize that the data files subject to \input{} are all machine generated, and regenerated often. And the which items appear in the legend is liable to change as well.

\documentclass{article}
\usepackage{graphics}
\usepackage{tikz}
\usepackage{pgfplots}
\pgfplotsset{compat=1.14}

\begin{document}

\begin{figure}[ht]
\centering
\begin{tabular}{ll}
\scalebox{0.8}{
% scatter plot of member decompose-types-bdd-graph-weak member types
\begin{tikzpicture}
%% label =
\begin{axis}[
lua backend=false,
xmode=log,
title=member decompose-types-bdd-graph-weak,
xlabel=execution time (seconds),
ylabel=profile percentage,
% legend style={at={(0.5,-0.2)},anchor=north},
label style={font=\tiny}
]
% bdd-to-expr
(3.203125e-4, 29.268291)
(1.004, 2.255017)
(8.587, 0.96698666)
};
% reduce-member-type
(3.203125e-4, 14.634146)
(8.587, 21.162012)
};
% subtypep
(3.203125e-4, 4.878049)
(8.587, 16.457731)
};
%\legend{bdd-to-expr,cmp-objects,delete-green-line,reduce-member-type,subtypep}
\end{axis}
\end{tikzpicture}
}
& \scalebox{0.8}{
% scatter plot of member bdd-decompose-types-weak member types
\begin{tikzpicture}
%% label =
\begin{axis}[
lua backend=false,
xmode=log,
title=member bdd-decompose-types-weak,
xlabel=execution time (seconds),
ylabel=profile percentage,
% legend style={at={(0.5,-0.2)},anchor=north},
label style={font=\tiny}
]
% alphabetize
(4.1015624e-4, 33.316727)
(0.025375, 16.120735)
(1.936, 2.504264)
(20.795, 1.0403473)
};
% smarter-subtypep-caching-call
(0.226, 19.634039)
(12.415, 74.85451)
(20.795, 77.89078)
};
%\legend{alphabetize,bdd-find,bdd-to-expr,reduce-member-type,smarter-subtypep-caching-call}
\end{axis}
\end{tikzpicture}
}\\
\scalebox{0.8}{
% scatter plot of member decompose-types-bdd-graph-weak-dynamic member types
\begin{tikzpicture}
%% label =
\begin{axis}[
lua backend=false,
xmode=log,
title=member decompose-types-bdd-graph-weak-dynamic,
xlabel=execution time (seconds),
ylabel=profile percentage,
% legend style={at={(0.5,-0.2)},anchor=north},
label style={font=\tiny}
]
% bdd-find-int-int
(1.3671875e-4, 17.142859)
(3.868, 1.7845836)
(14.327, 0.8117956)
};
% reduce-member-type
(0.006625, 12.331536)
(3.868, 26.627392)
(14.327, 13.590855)
};
% subtypep
(0.0015078125, 1.5544041)
(1.009, 28.728052)
(7.583, 16.043768)
(14.327, 11.326195)
};
%\legend{bdd-find-int-int,cmp-objects,delete-green-line,reduce-member-type,subtypep}
\end{axis}
\end{tikzpicture}
}
& \scalebox{0.8}{
% scatter plot of member bdd-decompose-types-weak-dynamic member types
\begin{tikzpicture}
%% label =
\begin{axis}[
lua backend=false,
xmode=log,
title=member bdd-decompose-types-weak-dynamic,
xlabel=execution time (seconds),
ylabel=profile percentage,
legend style={at={(0.5,-0.2)},anchor=north},
label style={font=\tiny}
]
% bdd-find
(7.8125e-5, 24.626438)
(7.846, 0.44732192)
(9.174, 0.44066837)
(18.655, 0.22309807)
};
% bdd-new-hash
(7.8125e-5, 10.902778)
(2.265625e-4, 9.69175)
(8.828125e-4, 3.5398233)
(0.009078125, 81.23925)
};
% smarter-subtypep-caching-call
(0.0145, 1.7241381)
(0.369, 27.886024)
(0.67, 34.817093)
(18.655, 72.91184)
};
\legend{bdd-find,bdd-new-hash,smarter-subtypep-caching-call}
\end{axis}
\end{tikzpicture}
}
\end{tabular}
\caption{Performance Profile \texttt{member} types using the
bdd-graph algorithm and rte algorithm, with different bdd cache
allocation strategies}
\end{figure}

\end{document}

• Have you tried group plots for this? – marmot Jul 29 '18 at 11:21
• @marmot, no I'd never heard of that, but it looks promising. I'll check it out. Thanks for the clue. – Jim Newton Jul 29 '18 at 12:31
• Here is an example of something that seems to go in the same direction (if I am not mistaken ;-). – marmot Jul 29 '18 at 12:39
• In your comment, were you wanting to add legend entries from more than one group plot, but only reference one legend? – aeroNotAuto Jul 30 '18 at 12:28
• @aeroNotauto, not sure I understand your question. I indeed want one single legend, but the legend should contain information about all the plots on all the groupplots. The exception is that if a legend entry exists with the same text on two different group plots, i want it to only appear once in the legend. – Jim Newton Jul 30 '18 at 12:32

Here is a proposal based on this answer, which seems to be initiated by a comment by John Kormylo. I am not claiming that group plots are essential to this solution, the main point is that one can produce an universal legend with \ref. And group plots certainly do not hurt here.

\documentclass{article}
\usepackage[margin=1in]{geometry} % added because the titles of the group plots are rather wide
\usepackage{tikz}
\usepackage{pgfplots}
\usepgfplotslibrary{groupplots}
\pgfplotsset{compat=1.16}

\begin{document}

\begin{figure}[ht]
\centering
% scatter plot of member decompose-types-bdd-graph-weak member types
\begin{tikzpicture}[scale=0.8,transform shape]
\begin{groupplot}[
group style={
group name=my plots,
group size=2 by 2,
xlabels at=edge bottom,
ylabels at=edge left,
horizontal sep=2cm,
vertical sep=3cm,
},
width=0.5\linewidth
]
%% label =
\nextgroupplot[lua backend=false,
xmode=log,
title=member decompose-types-bdd-graph-weak,
xlabel=execution time (seconds),
ylabel=profile percentage,
% legend style={at={(0.5,-0.2)},anchor=north},
label style={font=\tiny}]
% bdd-to-expr
(3.203125e-4, 29.268291)
(1.004, 2.255017)
(8.587, 0.96698666)
};
% reduce-member-type
(3.203125e-4, 14.634146)
(8.587, 21.162012)
};
% subtypep
(3.203125e-4, 4.878049)
(8.587, 16.457731)
};
%\legend{bdd-to-expr,cmp-objects,delete-green-line,reduce-member-type,subtypep}
\nextgroupplot[lua backend=false,
xmode=log,
title=member bdd-decompose-types-weak,
xlabel=execution time (seconds),
ylabel=profile percentage,
% legend style={at={(0.5,-0.2)},anchor=north},
label style={font=\tiny}
]
% alphabetize
(4.1015624e-4, 33.316727)
(0.025375, 16.120735)
(1.936, 2.504264)
(20.795, 1.0403473)
};
% smarter-subtypep-caching-call
(0.226, 19.634039)
(12.415, 74.85451)
(20.795, 77.89078)
};
%\legend{alphabetize,bdd-find,bdd-to-expr,reduce-member-type,smarter-subtypep-caching-call}
\nextgroupplot[lua backend=false,
xmode=log,
title=member decompose-types-bdd-graph-weak-dynamic,
xlabel=execution time (seconds),
ylabel=profile percentage,
% legend style={at={(0.5,-0.2)},anchor=north},
label style={font=\tiny}
]
% bdd-find-int-int
(1.3671875e-4, 17.142859)
(3.868, 1.7845836)
(14.327, 0.8117956)
};
% reduce-member-type
(0.006625, 12.331536)
(3.868, 26.627392)
(14.327, 13.590855)
};
% subtypep
(0.0015078125, 1.5544041)
(1.009, 28.728052)
(7.583, 16.043768)
(14.327, 11.326195)
};
%\legend{bdd-find-int-int,cmp-objects,delete-green-line,reduce-member-type,subtypep}
\nextgroupplot[lua backend=false,
xmode=log,
title=member bdd-decompose-types-weak-dynamic,
xlabel=execution time (seconds),
ylabel=profile percentage,
legend style={at={(0.5,-0.2)},anchor=north},
label style={font=\tiny},
legend to name=UniversalLegend
]
% bdd-find
(7.8125e-5, 24.626438)
(7.846, 0.44732192)
(9.174, 0.44066837)
(18.655, 0.22309807)
};
% bdd-new-hash
(7.8125e-5, 10.902778)
(2.265625e-4, 9.69175)
(8.828125e-4, 3.5398233)
(0.009078125, 81.23925)
};
% smarter-subtypep-caching-call
(0.0145, 1.7241381)
(0.369, 27.886024)
(0.67, 34.817093)
(18.655, 72.91184)
};
\legend{bdd-find,bdd-new-hash,smarter-subtypep-caching-call}
\end{groupplot}
\end{tikzpicture}\\
\ref{UniversalLegend}
\caption{Performance Profile \texttt{member} types using the
bdd-graph algorithm and rte algorithm, with different bdd cache
allocation strategies}
\end{figure}

\end{document}


UPDATE: As for your last comment, here is a modification of your code that builds up the legend. Whenever you have some new element, you only need to add it to an internal list with the command \\AddLegendEntryForLaterUse. This command takes two values, the legend entry and its style. (In the present version of your question, these styles are from a cycle list, but if I understand you correctly, ultimately you want to change this. If not, then I think the first part of my answer will allow you to get what you want.) In the last axis, the list that was built up is being processed to produce a full-fledged legend. (As usual, the main issue are the expansion problems.)

\documentclass{article}
\usepackage{tikz}
\usepackage{pgfplots}
\pgfplotsset{compat=1.16}
\newcounter{mylegends}
\begin{document}
\xdef\myLegendEntries{}
\xdef\myLegendEntries{#1/{#2}}
\else
\xdef\myLegendEntries{\myLegendEntries,#1/{#2}}
\fi
\stepcounter{mylegends}}
\begin{figure}[ht]
\centering
\begin{tabular}{ll}
\scalebox{0.8}{
% scatter plot of member decompose-types-bdd-graph-weak member types
\begin{tikzpicture}
%% label =
\begin{axis}[
lua backend=false,
xmode=log,
title=member decompose-types-bdd-graph-weak,
xlabel=execution time (seconds),
ylabel=profile percentage,
% legend style={at={(0.5,-0.2)},anchor=north},
label style={font=\tiny}
]
% bdd-to-expr
(3.203125e-4, 29.268291)
(1.004, 2.255017)
(8.587, 0.96698666)
};
% reduce-member-type
(3.203125e-4, 14.634146)
(8.587, 21.162012)
};
% subtypep
(3.203125e-4, 4.878049)
(8.587, 16.457731)
};
%\legend{bdd-to-expr,cmp-objects,delete-green-line,reduce-member-type,subtypep}
\end{axis}
\end{tikzpicture}
}
& \scalebox{0.8}{
% scatter plot of member bdd-decompose-types-weak member types
\begin{tikzpicture}
%% label =
\begin{axis}[
lua backend=false,
xmode=log,
title=member bdd-decompose-types-weak,
xlabel=execution time (seconds),
ylabel=profile percentage,
% legend style={at={(0.5,-0.2)},anchor=north},
label style={font=\tiny}
]
% alphabetize
(4.1015624e-4, 33.316727)
(0.025375, 16.120735)
(1.936, 2.504264)
(20.795, 1.0403473)
};
% smarter-subtypep-caching-call
(0.226, 19.634039)
(12.415, 74.85451)
(20.795, 77.89078)
};
%\legend{alphabetize,bdd-find,bdd-to-expr,reduce-member-type,smarter-subtypep-caching-call}
\end{axis}
\end{tikzpicture}
}\\
\scalebox{0.8}{
% scatter plot of member decompose-types-bdd-graph-weak-dynamic member types
\begin{tikzpicture}
%% label =
\begin{axis}[
lua backend=false,
xmode=log,
title=member decompose-types-bdd-graph-weak-dynamic,
xlabel=execution time (seconds),
ylabel=profile percentage,
% legend style={at={(0.5,-0.2)},anchor=north},
label style={font=\tiny}
]
% bdd-find-int-int
(1.3671875e-4, 17.142859)
(3.868, 1.7845836)
(14.327, 0.8117956)
};
% reduce-member-type
(0.006625, 12.331536)
(3.868, 26.627392)
(14.327, 13.590855)
};
% subtypep
(0.0015078125, 1.5544041)
(1.009, 28.728052)
(7.583, 16.043768)
(14.327, 11.326195)
};
%\legend{bdd-find-int-int,cmp-objects,delete-green-line,reduce-member-type,subtypep}
\end{axis}
\end{tikzpicture}
}
& \scalebox{0.8}{
% scatter plot of member bdd-decompose-types-weak-dynamic member types
\begin{tikzpicture}
%% label =
\begin{axis}[
lua backend=false,
xmode=log,
title=member bdd-decompose-types-weak-dynamic,
xlabel=execution time (seconds),
ylabel=profile percentage,
legend style={at={(0.5,-0.2)},anchor=north},
label style={font=\tiny},
legend to name=UniversalLegend
]
% bdd-find
(7.8125e-5, 24.626438)
(7.846, 0.44732192)
(9.174, 0.44066837)
(18.655, 0.22309807)
};
% bdd-new-hash
(7.8125e-5, 10.902778)
(2.265625e-4, 9.69175)
(8.828125e-4, 3.5398233)
(0.009078125, 81.23925)
};
% smarter-subtypep-caching-call
(0.0145, 1.7241381)
(0.369, 27.886024)
(0.67, 34.817093)
(18.655, 72.91184)
};
\foreach \X/\Y in \myLegendEntries
\temp
\temp
}
\end{axis}
\end{tikzpicture}
}\\
\multicolumn{2}{c}{\ref{UniversalLegend}}
\end{tabular}
\caption{Performance Profile \texttt{member} types using the
bdd-graph algorithm and rte algorithm, with different bdd cache
allocation strategies}
\end{figure}
\end{document}


• Thanks marmot, I will investigate. But it looks very nice. I think, from looking at the reference you mention, I need to unify the legend entries myself in the case the multiple plots share a curve of the same legend name, using non-repeating calls to \addlegendentry{}. That's not too difficult as I'm machine-generating them anyway. – Jim Newton Jul 29 '18 at 15:44
• Hi marmot, there's still something which is confusing to me. perhaps you could clarify for me. Each invocation of \nextgrouppot increments the "cursor" to the next plot in the grid, and \addplot generates a curve in the "current" plot. I want to associate a piece of text with each such plot. How do I indicate that THAT text goes into the legend? and after the successive \nextgrouppot I might associate the exact same text with another call to \addplot. I don't want that to appear duplicated in the legend. Is it me who must uniquify the legend? – Jim Newton Jul 30 '18 at 9:22
• @JimNewton I am not sure I fully understand your request. In the present example, there are three types of lines, and each of them has a legend entry. Or are you saying you wish to collect different pieces from different group plots? (I am not sure if anybody has done that yet, the best I could find is here. That means a possible solution may be involved, so I would like to check first why you need it and if one could perhaps avoid it.) – marmot Jul 30 '18 at 18:18
• @JimNewton This plan sounds very reasonable. You can use \addlegendentry and \addlegendpicture to build up your custom legend, and can use the legend to name=... and \ref{...} trick to place it wherever you like. One can build up lists with \xdef. If you append an example of your output to your question I will be happy to give it a try. – marmot Jul 30 '18 at 19:37
• @JimNewton I added another code to my answer which allows you to build a legend from elements of several axis environments. Is this closer to what you want? – marmot Jul 31 '18 at 3:30