4

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}[...]
\addplot[...]{...}
\addplot[...]{...}
\addplot[...]{...}
\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.

enter image description here

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

enter image description here

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
          \addplot+[] coordinates {
            (3.203125e-4, 29.268291)
            (1.004, 2.255017)
            (8.587, 0.96698666)
          };
          % reduce-member-type
          \addplot+[] coordinates {
            (3.203125e-4, 14.634146)
            (8.587, 21.162012)
          };
          % subtypep
          \addplot+[] coordinates {
            (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
          \addplot+[] coordinates {
            (4.1015624e-4, 33.316727)
            (0.025375, 16.120735)
            (1.936, 2.504264)
            (20.795, 1.0403473)
          };
          % smarter-subtypep-caching-call
          \addplot+[] coordinates {
            (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
          \addplot+[] coordinates {
            (1.3671875e-4, 17.142859)
            (3.868, 1.7845836)
            (14.327, 0.8117956)
          };
          % reduce-member-type
          \addplot+[] coordinates {
            (0.006625, 12.331536)
            (3.868, 26.627392)
            (14.327, 13.590855)
          };
          % subtypep
          \addplot+[] coordinates {
            (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
          \addplot+[] coordinates {
            (7.8125e-5, 24.626438)
            (7.846, 0.44732192)
            (9.174, 0.44066837)
            (18.655, 0.22309807)
          };
          % bdd-new-hash
          \addplot+[] coordinates {
            (7.8125e-5, 10.902778)
            (2.265625e-4, 9.69175)
            (8.828125e-4, 3.5398233)
            (0.009078125, 81.23925)
          };
          % smarter-subtypep-caching-call
          \addplot+[] coordinates {
            (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? –  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 ;-). –  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
  • Unfortunately, this appears to be beyond my level of expertise. The legend to name functionality appears to be designed to be called from only 1 plot in a group plot, assuming all plots have the same legend entries. I also tried building a custom legend with \label{} and \ref{} and \addlegendentry{} and \addlegendimage{} but it appears each plot in group plot has a different namespace/one plot in group plot can't see another plot's labels? Not sure if externalize will help somehow? – aeroNotAuto Jul 31 '18 at 00:57
  • If you do end up solving this, it would be interesting to see what you get to work/please post your answer here even if you're the only one that ends up answering it – aeroNotAuto Jul 31 '18 at 00:58

1 Answers1

4

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
          \addplot+[] coordinates {
            (3.203125e-4, 29.268291)
            (1.004, 2.255017)
            (8.587, 0.96698666)
          };
          % reduce-member-type
          \addplot+[] coordinates {
            (3.203125e-4, 14.634146)
            (8.587, 21.162012)
          };
          % subtypep
          \addplot+[] coordinates {
            (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
          \addplot+[] coordinates {
            (4.1015624e-4, 33.316727)
            (0.025375, 16.120735)
            (1.936, 2.504264)
            (20.795, 1.0403473)
          };
          % smarter-subtypep-caching-call
          \addplot+[] coordinates {
            (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
          \addplot+[] coordinates {
            (1.3671875e-4, 17.142859)
            (3.868, 1.7845836)
            (14.327, 0.8117956)
          };
          % reduce-member-type
          \addplot+[] coordinates {
            (0.006625, 12.331536)
            (3.868, 26.627392)
            (14.327, 13.590855)
          };
          % subtypep
          \addplot+[] coordinates {
            (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
          \addplot+[] coordinates {
            (7.8125e-5, 24.626438)
            (7.846, 0.44732192)
            (9.174, 0.44066837)
            (18.655, 0.22309807)
          };
          % bdd-new-hash
          \addplot+[] coordinates {
            (7.8125e-5, 10.902778)
            (2.265625e-4, 9.69175)
            (8.828125e-4, 3.5398233)
            (0.009078125, 81.23925)
          };
          % smarter-subtypep-caching-call
          \addplot+[] coordinates {
            (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}

enter image description here

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{}
\newcommand{\AddLegendEntryForLaterUse}[2]{\ifnum\value{mylegends}=0
\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
          \addplot+[] coordinates {
            (3.203125e-4, 29.268291)
            (1.004, 2.255017)
            (8.587, 0.96698666)
          };
          % reduce-member-type
          \addplot+[] coordinates {
            (3.203125e-4, 14.634146)
            (8.587, 21.162012)
          };
          % subtypep
          \addplot+[] coordinates {
            (3.203125e-4, 4.878049)
            (8.587, 16.457731)
          };
          \AddLegendEntryForLaterUse{something}{red,mark=*}
          %\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
          \addplot+[] coordinates {
            (4.1015624e-4, 33.316727)
            (0.025375, 16.120735)
            (1.936, 2.504264)
            (20.795, 1.0403473)
          };
          \AddLegendEntryForLaterUse{something else}{blue,mark=o}
          % smarter-subtypep-caching-call
          \addplot+[] coordinates {
            (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
          \addplot+[] coordinates {
            (1.3671875e-4, 17.142859)
            (3.868, 1.7845836)
            (14.327, 0.8117956)
          };
          % reduce-member-type
          \addplot+[] coordinates {
            (0.006625, 12.331536)
            (3.868, 26.627392)
            (14.327, 13.590855)
          };
          % subtypep
          \addplot+[] coordinates {
            (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
          \addplot+[] coordinates {
            (7.8125e-5, 24.626438)
            (7.846, 0.44732192)
            (9.174, 0.44066837)
            (18.655, 0.22309807)
          };
          % bdd-new-hash
          \addplot+[] coordinates {
            (7.8125e-5, 10.902778)
            (2.265625e-4, 9.69175)
            (8.828125e-4, 3.5398233)
            (0.009078125, 81.23925)
          };
          % smarter-subtypep-caching-call
          \addplot+[] coordinates {
            (0.0145, 1.7241381)
            (0.369, 27.886024)
            (0.67, 34.817093)
            (18.655, 72.91184)
          };
          \foreach \X/\Y in \myLegendEntries
          {\edef\temp{\noexpand\addlegendimage{\Y}}
           \temp
           \edef\temp{\noexpand\addlegendentry{\X}}
           \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}

enter image description here

  • 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 09: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.) –  Jul 30 '18 at 18:18
  • Hi marmot, I can explain my use case. Each group contains the functions profiled while calling a different top level function implementing a certain algorithm. Often the different algorithms use the same functions, often they do not. Each plot shows percentage-wise which were the HOT functions for that algo. The purpose of the page of graphs is to convey, which algos use which functions most heavily. I've colored to the curves so that function X is always colorX and function Y is always color Y. However not all groups have colorX. I'd like a legend of all colors used. – Jim Newton Jul 30 '18 at 19:31
  • My current plan is to continue to use the \begin{tabular}...\end{tabular} to make the grid of plots, each void of legend, and use https://tex.stackexchange.com/questions/258840/how-to-only-show-legend-in-pgf-plot to create a stand-along legend.

    Not sure if there's a better way. If there is it is not obvious from the groupplot documentation.

    – Jim Newton Jul 30 '18 at 19:33
  • 1
    @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. –  Jul 30 '18 at 19:37
  • I looked at the examples and read through much of the documentation, but I really still don't understand the semantics of \ref and "legend to name" – Jim Newton Jul 30 '18 at 20:42
  • 1
    @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? –  Jul 31 '18 at 03:30
  • thanks for the example. Just for clarification, if I want to create several completely different figures using this method, is the idea that I need to put the \xdef stuff in an include file where it's loaded once, or should I repeat it 10 times for 10 different figures? Do I need to use different variable names for each figure? – Jim Newton Jul 31 '18 at 06:55
  • @JimNewton You can simply reset the list by saying \xdef\myLegendEntries{} and \setcounter{mylegends}{0}. –  Jul 31 '18 at 11:02