2

I want to use pgfplots to plot a groupplot who's width (8 cm), font family (Arial), and font sizes (10pt for the axis labels, 8pt for the tick labels) are specific.

I've tried tikzscale to get the dimension of the figure right. And for the fonts I used fontspec. While I can get the figure size right as well as the font family, the font sizes are always a bit off (9.79586506pt and 7.83669019pt for the 10pt and 8pt respectively).

The main .tex document:

\documentclass[10pt]{standalone} % set the normalsize to 10 pt
\usepackage{pgfplots}
\pgfplotsset{compat=newest} 
\usepgfplotslibrary{groupplots}
\usepackage{tikzscale}

\usepackage{fontspec}
\setmainfont{Arial} % set the font family to Arial

\pgfplotsset{
             every axis label = {font=\normalsize}, % set the axis label font to 10pt
             tick label style={/pgf/number format/assume math mode=true, font=\footnotesize} % set the tick label font to 8pt
}

\begin{document}

\includegraphics[width = 8cm]{a.tikz}

\end{document}

The .tikz file:

\begin{tikzpicture}
    \begin{groupplot}[
        group style={group size=2 by 2,
            horizontal sep = 1 cm, 
            vertical sep = 1 cm}, 
        xlabel = X label,
        ylabel = Y label,
        width = 3 cm, % this is supposed to be total width (8cm) divided by number of columns (2) minus the sum of all the horizontal separations (1cm), i.e. 3 cm
        height = 3 cm, % same as above, but for the vertical
        ]
        \nextgroupplot%[xticklabels = {1,}]
        \addplot coordinates {(0,0) (1,1) (2,2)};
        \nextgroupplot
        \addplot coordinates {(0,2) (1,1) (2,0)};
        \nextgroupplot
        \addplot coordinates {(0,2) (1,1) (2,1)};
        \nextgroupplot
        \addplot coordinates {(0,2) (1,1) (1,0)};      
    \end{groupplot}
\end{tikzpicture}

results

Note 1: I suspect that part of the problem is the initial sizes of each of the individual plots in the groupplot. I followed these instructions. But I think we would need to take into account the stuff on the left of the left-most axis -- namely the y tick labels and the y label. Maybe then it'll come out right?

Note 2: In fact, the two font sizes are off by the same factor. So 8pt and 10pt are actually a*8pt and a*10pt where a is some number. I managed to tweak the result by using a factor of 1.0245 in the following:

\setmainfont[Scale = 1.0245]{Arial}
\defaultfontfeatures{ Scale = MatchUppercase }

While this "works" it's very tedious and works only for this specific example. If I change the figure or its size this methods breaks.

Update 1

Following Stefan Pinnow's solution, an added complexity occurs when the tick labels of the x axis protrude outside the right-most side of the box of the right-most pane. This can easily occur when the coordinate include large numbers. For example:

\documentclass[10pt]{standalone}
\usepackage{fontspec}
    \setmainfont{Arial} % set the font family to Arial
\usepackage{pgfplots}
    \usepgfplotslibrary{groupplots}
    \pgfplotsset{
        % use this `compat` level or higher to get this result
        compat=1.3,
        every axis label={font=\normalsize}, % set the axis label font to 10pt
        tick label style={
            /pgf/number format/assume math mode=true,
            font=\footnotesize,     % set the tick label font to 8pt
        },
    }
\begin{document}
\begin{tikzpicture}
        % define a length to set the (y) label distance
        \pgfmathsetlengthmacro{\LabelDistance}{30pt}
    \begin{groupplot}[
        group style={
            group size=2 by 2,
            horizontal sep=1cm,
            vertical sep=1cm,
        },
        % added so the ylabels are aligned properly and to define a distance
        % to the axis line, which can be used to calculate the `width`
        every axis y label/.style={
            at={(ticklabel* cs:0.5,\LabelDistance)},
            rotate=90,
            anchor=near ticklabel opposite,
        },
        % use `width` to the axis only, i.e. without any labels
        scale only axis,
        % calculate the with of the single plots depending on the known/defined
        % values for the total width of the groupplot, the distance of the
        % ylabels (`\LabelDistance`) and `horizontal sep` of the plots
        width={(8cm - \LabelDistance - \pgfkeysvalueof{/pgfplots/group/horizontal sep})/2},
        % give the same value as the `width` (not sure is this is required)
        height={\pgfkeysvalueof{/pgfplots/width}},
        xlabel=X label,
        ylabel=Y label,
    ]
    \nextgroupplot
        \addplot coordinates {(0,0) (1,1) (2,2)};
    \nextgroupplot
        \addplot coordinates {(0,2) (1,1) (2,1)};
    \nextgroupplot
        \addplot coordinates {(0,2) (1,1) (2,1)};
    \nextgroupplot
        \addplot coordinates {(0,2) (1,1) (1000,0)};
    \end{groupplot}
\end{tikzpicture}
\end{document}

Which results in a understandably wider pdf file, with a width of 8.214 cm (instead of 8 cm):

wider pdf

2 Answers 2

2

New version:

New 1: You need to measure out the label distances.

Then you can calculate a correct \TotalWidth for values

% My Values =========================
\pgfmathsetlengthmacro\horizontalsep{15pt}
\pgfmathsetlengthmacro\verticalsep{30pt}
\pgfmathsetlengthmacro\mywidth{200pt}
\pgfmathsetlengthmacro\myheight{120pt}

enter image description here

New 2: The measure method is now a style
Measure/.style={...},,
which you can add in an arbitrary
\nextgroupplot[...., Measure] % should be the last key
You should use the \nextgroupplot with the biggest ticks and labels.

If you do not set a ylabel (or xlabel), there is the problem that he can't find the coordinate at brown (yellow) point. So we should set a default
xlabel={\phantom{.}}, ylabel={\phantom{.}}
this gives a gap of ca. 2pt (due to the inner sep), if the label was not setted.

enter image description here

With \def\ShowHelps{0} % 1 "yes" 0 "no"

enter image description here

\documentclass[a4paper]{article}
\usepackage[margin=1cm, showframe=false]{geometry}
\usepackage{pgfplots}
\pgfplotsset{compat=newest}
\usepgfplotslibrary{groupplots}
\pgfkeys{/tikz/savevalue/.code 2 args={\global\edef#1{#2}}}
%\usetikzlibrary{backgrounds}
\pgfdeclarelayer{background}
\pgfdeclarelayer{foreground}
\pgfsetlayers{background,main,foreground}

% My Values =========================
\pgfmathsetlengthmacro\horizontalsep{15pt}
\pgfmathsetlengthmacro\verticalsep{30pt}
\pgfmathsetlengthmacro\mywidth{200pt}
\pgfmathsetlengthmacro\myheight{120pt}

\def\ShowHelps{1}  % 1 "yes"    0 "no"

\renewcommand*\familydefault{\sfdefault}  
% ===============================

\ifnum\ShowHelps=1 
\def\ptsize{1.5pt}
\tikzset{Help/.style={draw=#1,  
nodes={scale=0.6, fill=yellow, draw=#1, text=black, font=\tiny, midway, below, align=left, inner sep=0.5pt} },
Help/.default=red, 
HelpDraw/.style={draw},
}
\else
\def\ptsize{0pt}
\tikzset{Help/.style={opacity=0, overlay},
HelpDraw/.style={},
}\fi

\pgfplotsset{
NeededStylesForMeasure/.style={
xlabel={\phantom{.}},  ylabel={\phantom{.}}, % "default"
every axis label/.append style={
inner sep=1pt % abs. value, regular default = 0.333em
},     },
}

\pgfplotsset{
Measure/.style={%%%
every axis label/.append style={HelpDraw},
every tick label/.append style={HelpDraw},
before end axis/.code={
\fill[red] (yticklabel* cs:0.5) coordinate (X2) circle(\ptsize); 
\fill[orange] (xticklabel* cs:0.5) coordinate (Y2) circle(\ptsize); 
}, 
every axis y label/.append style={  
path picture={
\begin{pgfonlayer}{foreground}
\fill[brown] (path picture bounding box.west) coordinate(X1) circle(\ptsize);
\end{pgfonlayer}    
\path let \p1=($(X2)-(X1)$) in 
\pgfextra{  \pgfmathsetlengthmacro{\ylabeldistance}{veclen(\x1,\y1)}   }
[savevalue={\yLabelDistance}{\ylabeldistance}];          
},
},
every axis x label/.append style={  
path picture={
\begin{pgfonlayer}{foreground}
\fill[yellow!88!red] (path picture bounding box.south) coordinate(Y1) circle(\ptsize);
\end{pgfonlayer}    
\path let \p1=($(Y2)-(Y1)$) in 
\pgfextra{  \pgfmathsetlengthmacro{\xlabeldistance}{veclen(\x1,\y1)}   }
[savevalue={\xLabelDistance}{\xlabeldistance}];          
},
},
},%%%
}

% For wrong picture
\pgfmathsetlengthmacro\HorizontalSep{\horizontalsep}
\pgfmathsetlengthmacro\VerticalSep{\verticalsep}

\pgfmathsetlengthmacro\AxisWidth{\mywidth}
\pgfmathsetlengthmacro\AxisHeight{\myheight}

\newcommand\mygroupplot{%%%%%%%%%%%%%%%%
\noindent\begin{tikzpicture}
\begin{groupplot}[group style={
group size={2 by 2},
horizontal sep=\HorizontalSep, vertical sep=\VerticalSep,
}, 
scale only axis, %1/2
width=\AxisWidth, height=\AxisHeight, %2/2
NeededStylesForMeasure,
clip=false, 
]
\nextgroupplot[title={1}, ylabel=yLabel1, xlabel=x, 
y label style={font=\Huge,},  % Test
%ylabel shift=5pt, % Test 2
Measure
]
\addplot coordinates {(0,-500) (1,1) (2,2)};

\nextgroupplot[title={2}, ylabel=yLabel2, xlabel=x]
\addplot coordinates {(0,2) (1,1) (2,1)};
\nextgroupplot[title={3}]
\addplot coordinates {(0,2) (1,1) (1,0)};
\nextgroupplot[title={4}]
\addplot[]{3*x};
\end{groupplot}

% ==========================================
% The Helping Stuff ===============================
% x- and  y Label Distances
\draw[Help] (X1) -- (X2) node[Help]{\yLabelDistance \\ yLabelDistance};   
\draw[Help] (Y1) -- (Y2) node[Help, right]{\xLabelDistance \\ xLabelDistance};     

% Width
\pgfmathsetlengthmacro{\width}{\AxisWidth+\yLabelDistance}
\node[savevalue={\Width}{\width}]{};
\draw[Help]  (group c1r1.north east) -- +(-\Width,0) node[Help]{\Width \\ Width};
\draw[Help]  (group c1r1.south east) -- +(-\AxisWidth,0) node[Help, above]{\AxisWidth \\ AxisWidth};

% Height
\pgfmathsetlengthmacro{\height}{\AxisHeight+\xLabelDistance}
\node[savevalue={\Height}{\height}]{};
\draw[Help=blue, |-, transform canvas={yshift=-\xLabelDistance},
]  (group c1r1.south west) -- +(0,\Height) node[Help, right]{\Height \\ Height};
\draw[Help]  (group c1r1.south east) -- +(0,\AxisHeight) node[Help, left]{\AxisHeight \\ AxisHeight};

% groupplot boxes
\foreach \r in {1,2}{\foreach \c in {1,2}{
\draw[Help=pink, dashed]  ([xshift=-\yLabelDistance, yshift=-\xLabelDistance]group  c\c r\r.south west) rectangle +(\Width, \Height);
\draw[Help=green, dashed] (group c\c r\r.south east) rectangle +(-\AxisWidth,-\xLabelDistance);
}}

% complete plot box 
\draw[Help=purple, dashed]  ([xshift=-\yLabelDistance, yshift=-\xLabelDistance]group c1r2.south west) rectangle +(2*\Width+\horizontalsep, 2*\Height+\verticalsep);

% Horizontal Sep
\draw[Help, purple]  (group c1r1.south east) -- +(\HorizontalSep,0) node[Help, below, near end]{\HorizontalSep \\ HorizontalSep};
\draw[Help, green, thin]  ([yshift=4mm]group c1r1.south east)  --  +(\horizontalsep,0) node[Help, draw=green, above=3pt,near end]{\horizontalsep \\ horizontalsep};

% Vertical Sep
\draw[Help=purple]  (group c1r1.south east) -- +(0,-\VerticalSep) node[Help, right]{\VerticalSep \\ VerticalSep};
\draw[Help, green, thin]  ([yshift=-\xLabelDistance, xshift=-4mm]group c1r1.south east)  --  +(0,-\verticalsep) node[Help, draw=green, left=3pt]{\verticalsep \\ verticalsep};

% Total Width
\pgfmathsetlengthmacro{\TotalWidth}{2*\Width+\horizontalsep}
\draw[Help=purple, thick]  ([xshift=-\yLabelDistance, yshift=-\xLabelDistance]group c1r2.south west) -- +(\TotalWidth, 0) node[Help=purple, above, scale=2]{ 
TotalWidth (\TotalWidth) = 2$\times$Width (2$\times$\Width) + horizontalsep (\horizontalsep)
};

% Total Height
\pgfmathsetlengthmacro{\TotalHeight}{2*\Height+1*\verticalsep}
\draw[Help=blue, very thick]  ([yshift=-\xLabelDistance]group c2r2.south east) -- +(0,\TotalHeight) node[Help=blue, above, rotate=90, scale=2]{
TotalHeight (\TotalHeight) =  2$\times$Height (2$\times$\Height) + horizontalsep (\verticalsep)
};
% ==========================================
% ==========================================
\end{tikzpicture}
}%%%%%%%%%%%%%%%%
\begin{document}
\section{Wrong}
\mygroupplot % wrong

\newsavebox{\mybox}
\sbox{\mybox}{\mygroupplot}
%\noindent\usebox{\mybox} % still wrong

\section{Correct}
\pgfmathsetlengthmacro\AxisWidth{\mywidth-\yLabelDistance}
\pgfmathsetlengthmacro\AxisHeight{\myheight-\xLabelDistance}
\pgfmathsetlengthmacro\HorizontalSep{\horizontalsep+\yLabelDistance}
\pgfmathsetlengthmacro\VerticalSep{\verticalsep+\xLabelDistance}
\sbox{\mybox}{\mygroupplot}
\noindent\usebox{\mybox} %now correct
\end{document}
1
  • This very documented answer would have deserved more recognition :)
    – JeT
    Commented Apr 17, 2021 at 13:37
1

I wouldn't recommend to use the tikzscale package. Here one way of achieving what you want. For details please have a look at the comments in the code.

% used PGFPlots v1.16
\documentclass[border=5pt,10pt]{standalone}
\usepackage{fontspec}
    \setmainfont{Arial} % set the font family to Arial
\usepackage{pgfplots}
    \usepgfplotslibrary{groupplots}
    \pgfplotsset{
        % use this `compat` level or higher to get this result
        compat=1.3,
        every axis label={font=\normalsize}, % set the axis label font to 10pt
        tick label style={
            /pgf/number format/assume math mode=true,
            font=\footnotesize,     % set the tick label font to 8pt
        },
    }
\begin{document}
\begin{tikzpicture}
        % define a length to set the (y) label distance
        \pgfmathsetlengthmacro{\LabelDistance}{30pt}
    \begin{groupplot}[
        group style={
            group size=2 by 2,
            horizontal sep=1cm,
            vertical sep=1cm,
        },
        % added so the ylabels are aligned properly and to define a distance
        % to the axis line, which can be used to calculate the `width`
        every axis y label/.style={
            at={(ticklabel* cs:0.5,\LabelDistance)},
            rotate=90,
            anchor=near ticklabel opposite,
        },
        % use `width` to the axis only, i.e. without any labels
        scale only axis,
        % calculate the with of the single plots depending on the known/defined
        % values for the total width of the groupplot, the distance of the
        % ylabels (`\LabelDistance`) and `horizontal sep` of the plots
        width={(8cm - \LabelDistance - \pgfkeysvalueof{/pgfplots/group/horizontal sep})/2},
        % give the same value as the `width` (not sure is this is required)
        height={\pgfkeysvalueof{/pgfplots/width}},
        xlabel=X label,
        ylabel=Y label,
    ]
    \nextgroupplot
        \addplot coordinates {(0,0) (1,1) (2,2)};
    \nextgroupplot
        \addplot coordinates {(0,2) (1,1) (2,1)};
    \nextgroupplot
        \addplot coordinates {(0,2) (1,1) (2,1)};
    \nextgroupplot
        \addplot coordinates {(0,2) (1,1) (1,0)};
    \end{groupplot}

    % -------------------------------------------------------------------------
    % for debugging purposes only
    % -------------------------------------------------------------------------
    \draw [red] (group c1r2.south east) -- +(1cm,0);

    \draw [blue,thick]  (current bounding box.south west |- 0,1) --
                       +(\LabelDistance,0pt);

    \draw [green]       (current bounding box.south west) rectangle
                        (current bounding box.north east);
    \draw [red,dashed]  (current bounding box.south west) rectangle +(8,8);
    % -------------------------------------------------------------------------
\end{tikzpicture}
\end{document}

image showing the result of above code

6
  • Awesome. Thank you. There is no doubt that the intersection between grouped plots, font size, and figure width is problematic in pgfplots. This excellent solution unfortunately breaks if the x axis includes larger numbers that protrude beyond the right-hand side of the box of the right-most pane (e.g. instead of 0, 0.5, and 1, we could have had 0, 500, and 1000). Or if an outer legend is included... I'm sure there are other edge-cases, but this at least works for now. Thanks again!
    – 12.yakir
    Commented Jan 22, 2020 at 9:12
  • Therefore I introduced the \LabelDistance length. Together with the horizontal sep you should be able to find a combination where the ylabel doesn't overlap with the yticklabels and the right ylabel doesn't intersect with the left axis border. Commented Jan 22, 2020 at 9:30
  • I might have not been clear. An overlap between the y label and the adjacent pane is not the problem. See my last edit, maybe that will be clearer.
    – 12.yakir
    Commented Jan 22, 2020 at 9:32
  • When one manual iteration would be ok for you, you could add another length, e.g. \pgfmathsetlengthmacro{\RightOffset}{0mm} and subtract this also in the formula of width. After knowing that now the total with is e.g. 8.214cm (as in your above example) you could change the value of \RightOffset to 2.14mm. Commented Jan 22, 2020 at 9:37
  • That approach would be great if we could tell pgfplots to just add a margin of some safe extraneous space on the right. Just as you did with \LabelDistance on the left (although there is no label on the right).
    – 12.yakir
    Commented Jan 22, 2020 at 9:44

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .