8

When using matlab2tikz to generate a .tikz from Matlab where the legend location is set to 'SouthOutside' (below the ylabel of figure), the position of the legend will cover the ylabel unless the legend position is manually shifted with the "at={(0.5,-0.17)}" in the "legend style" parameter of tikzpicture options. This issue has been brought up in the past on one of the developer's webpages. The package pgfplots allows you to easily adjust the width and height of the figure with the variables I chose to call "\figureheight" and "\figurewidth" so I figured I could create a linear curve fit to relate the legend position to the figure height. I was able to do this, but I can't figure out how to convert the variable "\figureheight" to a value which can be manipulated in the following way, where this line would be where it is in the MWE:

at={(0.5,(0.0704*\figurewidth - 0.3572)}

Since \figurewidth is in inches, I was wondering if there's a way to "strip" the unit and let it behave as a double, then multiply by 0.0704 and subtract 0.3572 Here's a MWE of the problem:

\documentclass{article}

\usepackage{pgfplots}
\usepackage{amsmath}

\newlength\figureheight
\newlength\figurewidth

\begin{document}

\setlength\figureheight{2.0in} 
\setlength\figurewidth{2.0in}
\begin{figure}
    \begin{tikzpicture}
        \begin{axis}[
        width=\figurewidth,
        height=\figureheight,
        xlabel={x label text},
        ylabel={y label text},
        legend style={at={(0.5,-0.17)},anchor=north,legend cell align=left} %
        ]

        \addplot coordinates {(0,0) (1,1)};
        \addlegendentry{Legend Entry 1}

        \addplot coordinates {(0,1) (1,2)};
        \addlegendentry{Legend Entry 2}

        \end{axis}
    \end{tikzpicture}
    \caption{2" tall figure, x label covered} %
\end{figure}
%now with a larger figure, the xlabel will no longer be hidden
\setlength\figureheight{3.0in} 
\setlength\figurewidth{3.0in}
\begin{figure}
    \begin{tikzpicture}
        \begin{axis}[
        width=\figurewidth,
        height=\figureheight,
        xlabel={x label text},
        ylabel={y label text},
        legend style={at={(0.5,-0.17)},anchor=north,legend cell align=left} %
        ]

        \addplot coordinates {(0,0) (1,1)};
        \addlegendentry{Legend Entry 1}

        \addplot coordinates {(0,1) (1,2)};
        \addlegendentry{Legend Entry 2}

        \end{axis}
    \end{tikzpicture}
    \caption{3" tall figure, x label visible} %
\end{figure}

\end{document}

The output looks like this:

enter image description here

Thanks in advance!

Solution provided by Jake see below for direct answer. Here is matlab2tikz modified to make these changes automatically: The only change from the unmodified matlab2tikz is when you have set(hlegend,'Location','SouthOutside') in Matlab. I added code in three sections:

near line 1200:

within if ~isempty(axisLabel) and under if length(label) > 1 statement:

m2t.axesContainers{end}.options = ...
            addToOptions(m2t.axesContainers{end}.options, ...
                        [axis, 'label style'], '{align=center}');
      end
        m2t.axesContainers{end}.options = ...%RESOLVE SOUTHOUTSIDE LEGEND ISSUES
            addToOptions(m2t.axesContainers{end}.options, ...%RESOLVE SOUTHOUTSIDE LEGEND ISSUES
            [axis, 'label style'], ['{name=' axis 'label}']);%RESOLVE SOUTHOUTSIDE LEGEND ISSUES

around line 3622:

case 'southoutside'
          position = [0.5, -outside_dist];
          position_string='xlabel.south';%RESOLVE SOUTHOUTSIDE LEGEND ISSUES
          anchor = 'north';

around line 3700:

% append to legend options
  if ~isempty(anchor) %RESOLVE SOUTHOUTSIDE LEGEND ISSUES
      if exist('position_string')%reference the xlabel so legend doesn't cover label when figure is scaled
          lStyle = {lStyle{:}, ...%RESOLVE SOUTHOUTSIDE LEGEND ISSUES
                sprintf('at={(%s)}', position_string), ...%RESOLVE SOUTHOUTSIDE LEGEND ISSUES
                sprintf('anchor=%s', anchor)};%RESOLVE SOUTHOUTSIDE LEGEND ISSUES
      else %hard code the leged position - works well for legends inside the axes
        lStyle = {lStyle{:}, ...
                sprintf('at={(%.15g,%.15g)}', position), ...
                sprintf('anchor=%s', anchor)};
      end
  end
  • I do not really know, what you want to achieve, but I guess that the chapter "Evaluating Mathematical Expressions" in the pgf manual could be of help. You can do the conversion of units manually, too, by multiplying or dividing by 2.54 (or whatever the exact factor is). Btw. avoiding non-SI units is a good thing. matlab2tikz can be used accordingly. – Patrick Häcker May 22 '13 at 8:44
10

Basically, what you are looking for is the key legend pos=outer south, which doesn't exist yet, as explained by the PGFPlots developer in the forum discussion you linked to. The reasons are that it's complicated to take into account whether there's an axis title below the axis or not, which has an influence on where to place the legend. However, it turns out that it's not actually that complicated: The following code snippet assigns a node alias to the x axis label, and then uses the code snippet from How can I know if a node is already defined? in the legend pos/outer south code to decide whether the axis title has to be taken into account or not:

\makeatletter
\pgfplotsset{
    every axis x label/.append style={
        alias=current axis xlabel
    },
    legend pos/outer south/.style={
        /pgfplots/legend style={
            at={%
                (%
                \@ifundefined{pgf@sh@ns@current axis xlabel}%
                {xticklabel cs:0.5}%
                {current axis xlabel.south}%
                )%
            },
            anchor=north
        }
    }
}
\makeatother

Full code:

\documentclass{article}

\usepackage{pgfplots}
\usepackage{amsmath}

\makeatletter
\pgfplotsset{
    every axis x label/.append style={
        alias=current axis xlabel
    },
    legend pos/outer south/.style={
        /pgfplots/legend style={
            at={%
                (%
                \@ifundefined{pgf@sh@ns@current axis xlabel}%
                {xticklabel cs:0.5}%
                {current axis xlabel.south}%
                )%
            },
            anchor=north
        }
    }
}
\makeatother

\newlength\figureheight
\newlength\figurewidth

\begin{document}

\setlength\figureheight{2.0in} 
\setlength\figurewidth{2.0in}
\begin{figure}\centering
    \begin{tikzpicture}[trim axis left]
        \begin{axis}[
        width=\figurewidth,
        height=\figureheight,
        xlabel={x label text},
        ylabel={y label text},clip=false,
        legend pos=outer south
        ]

        \addplot coordinates {(0,0) (1,1)};
        \addlegendentry{Legend Entry 1}

        \addplot coordinates {(0,1) (1,2)};
        \addlegendentry{Legend Entry 2}
        \end{axis}
    \end{tikzpicture}
    \caption{2" tall figure, x label covered} %
\end{figure}
%now with a larger figure, the xlabel will no longer be hidden
\setlength\figureheight{3.0in} 
\setlength\figurewidth{3.0in}
\begin{figure}\centering
    \begin{tikzpicture}[trim axis left]
        \begin{axis}[
        width=\figurewidth,
        height=\figureheight,
        xlabel={x label text},
        ylabel={y label text},
        legend pos=outer south
        ]

        \addplot coordinates {(0,0) (1,1)};
        \addlegendentry{Legend Entry 1}

        \addplot coordinates {(0,1) (1,2)};
        \addlegendentry{Legend Entry 2}

        \end{axis}
    \end{tikzpicture}
    \caption{3" tall figure, x label visible} %
\end{figure}

\end{document}

Alternatively you could place the legend relative to the xlabel node by first naming the node using xlabel style={name=xlabel} and then using legend style={ at={(xlabel.south)}, % Place legend relative to xlabel node yshift=-1ex, anchor=north }

That way, the legend will always be the same distance from the x axis label:

\documentclass{article}

\usepackage{pgfplots}
\usepackage{amsmath}

\newlength\figureheight
\newlength\figurewidth
\pgfplotsset{compat=newest}

\begin{document}

\setlength\figureheight{2.0in} 
\setlength\figurewidth{2.0in}
\begin{figure}
    \begin{tikzpicture}
        \begin{axis}[
        width=\figurewidth,
        height=\figureheight,
        xlabel={x label text},
        xlabel style={name=xlabel}, % Name the xlabel node
        ylabel={y label text},
        legend style={
                at={(xlabel.south)}, % Place legend relative to xlabel node
                yshift=-1ex,
                anchor=north,
                legend cell align=left
            } %
        ]

        \addplot coordinates {(0,0) (1,1)};
        \addlegendentry{Legend Entry 1}

        \addplot coordinates {(0,1) (1,2)};
        \addlegendentry{Legend Entry 2}

        \end{axis}
    \end{tikzpicture}
    \caption{2" tall figure, x label visible} %
\end{figure}
\setlength\figureheight{3.0in} 
\setlength\figurewidth{3.0in}
\begin{figure}
    \begin{tikzpicture}
        \begin{axis}[
        width=\figurewidth,
        height=\figureheight,
        xlabel={x label text},
        xlabel style={name=xlabel},
        ylabel={y label text},
        legend style={at={(xlabel.south)},yshift=-1ex, anchor=north,legend cell align=left} %
        ]

        \addplot coordinates {(0,0) (1,1)};
        \addlegendentry{Legend Entry 1}

        \addplot coordinates {(0,1) (1,2)};
        \addlegendentry{Legend Entry 2}

        \end{axis}
    \end{tikzpicture}
    \caption{3" tall figure, x label visible} %
\end{figure}

\end{document}
  • 1
    Oh wow, the usage of the xlabel anchor is very clever. – henry Sep 17 '14 at 20:41
  • Thanks for your answer, but what if I need to horizontally align the legend with the whole axis + ylabel not just axis, how can this be done? – Diaa Jan 26 '17 at 16:16
1

@Jack's answer is a very good starting point. Some minor issues are:

  • The predefined options of legend pos use .03, .97, and 1.03 as magic numbers. A backward-compatible outer south option should yield at={(.5,-.03)} when no xlabel/xtick is present.
  • x label, when is present, is not necessary put at the bottom. It depends on whether axis x line or xtick pos is modified.
  • current axis xlabel.south is not necessary the right place, especially when the text is rotated.

To overcome the last two issues, one may use current bounding box. For example

legend pos/outer south/.style={
    /pgfplots/legend style={
        at=({current bounding box.south-|.5,0}),
        anchor=north
    }
}

Notice that the axis environment is put inside a pgfinterruptboundingbox. Therefore current bounding box refers to the axis itself, instead of the parent tikzpicture.


To overcome the first issue, one can simply invoke (.5,-.03) and PGF will update the lower boundary automatically. For example

legend pos/outer south/.style={
    /pgfplots/legend style={
        /utils/exec={\pgfpathmoveto{\pgfpointxy{.5}{-.03}}},
        %/utils/exec={\useasboundingbox(.5,-.03);}, % same
        at=({current bounding box.south-|.5,0}),
        anchor=north
    }
}

Full code

\documentclass{article}

\usepackage{pgfplots}
%\usetikzlibrary{fit}
\pgfplotsset{compat=1.14}

\begin{document}

%\tikz[inner sep=0]{
%   \path(0,0)node(A)[draw,rotate=-30]{node0,0}
%        (1,1)node(B)[draw,rotate=30]{node1,1};
%   \node[fit=(A)(B),draw]{};
%}
%
%
%\tikz[inner sep=0]{
%   \path(0,0)node(A)[draw,rotate=-30]{node0,0}
%        (1,1)node(B)[draw,rotate=30]{node1,1};
%   \draw(current bounding box.south west)rectangle(current bounding box.north east);
%}

\makeatletter
\pgfplotsset{
    every axis x label/.append style={
        alias=current axis xlabel
    },
    legend pos/outer south/.style={
        /pgfplots/legend style={
            /utils/exec={\pgfpathmoveto{\pgfpointxy{.5}{-.03}}},
            %/utils/exec={\useasboundingbox(.5,-.03);}, % same
            at=({current bounding box.south-|.5,0}),
            anchor=north,
            %/utils/exec={\draw(.5,-.03)circle(.03);}
        }
    }
}

\begin{tikzpicture}
    %\draw(-1,-1)--(8,7); % this does not effect (current bounding box) inside \begin{axis}
    \begin{axis}[
            legend pos=outer south,
            xlabel={x label text},
            %x tick label style={rotate=45}
        ]
        \addplot coordinates {(0,0) (1,1)};
        \addplot coordinates {(0,1) (1,2)};
        \addplot coordinates {(0,2) (1,3)};
        \legend{$l_1$,$l_2$,$l_3$}
    \end{axis}
\end{tikzpicture}

\vfill

\begin{tikzpicture}
    %\draw(-1,-1)--(8,7); % this does not effect (current bounding box) inside \begin{axis}
    \begin{axis}[
            legend pos=outer south,
            xlabel={x label text},
            xtick pos=right,
            %x label style={rotate=45}
        ]
        \addplot coordinates {(0,0) (1,1)};
        \addplot coordinates {(0,1) (1,2)};
        \addplot coordinates {(0,2) (1,3)};
        \legend{$l_1$,$l_2$,$l_3$}
    \end{axis}
\end{tikzpicture}

\clearpage





\pgfplotsset{
    legend pos/outer north east v2/.style={
        /pgfplots/legend style={
            /utils/exec={\pgfpathmoveto{\pgfpointxy{1.03}{1}}},
            at=(current bounding box.north east),
            anchor=north west
        }
    },
    legend pos/outer north east v3/.style={
        /pgfplots/legend style={
            /utils/exec={\pgfpathmoveto{\pgfpointxy{1.03}{1}}},
            at={(current bounding box.north east|-0,1)},
            anchor=north west
        }
    }
}

\begin{tikzpicture}
    \begin{axis}[title=original,
                 legend pos=outer north east,
                 ytick pos=right]
        \addplot coordinates {(0,0) (1,1)};
        \addplot coordinates {(0,1) (1,2)};
        \addplot coordinates {(0,2) (1,3)};
        \legend{$l_1$,$l_2$,$l_3$}
    \end{axis}
\end{tikzpicture}

\begin{tikzpicture}
    \begin{axis}[title=version 2,
                 legend pos=outer north east v2,
                 ytick pos=right]
        \addplot coordinates {(0,0) (1,1)};
        \addplot coordinates {(0,1) (1,2)};
        \addplot coordinates {(0,2) (1,3)};
        \legend{$l_1$,$l_2$,$l_3$}
    \end{axis}
\end{tikzpicture}

\begin{tikzpicture}
    \begin{axis}[title=version 3,
                 legend pos=outer north east v3,
                 ytick pos=right]
        \addplot coordinates {(0,0) (1,1)};
        \addplot coordinates {(0,1) (1,2)};
        \addplot coordinates {(0,2) (1,3)};
        \legend{$l_1$,$l_2$,$l_3$}
    \end{axis}
\end{tikzpicture}

\end{document}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.