[Tex/LaTex] Make pgfplots typeset axis units as reciprocals using siunitx

pgfplotssiunitx

pgfplots has a library units that allows to typeset units in axis labels provided through x unit=<unit>. Through the key \pgfplotsset{unit code/.code={\si{#1}}}, it can be made to typeset the units using the excellent siunitx.

My problem is the following: When I provide units with negative exponents, such as \metre\per\second, they are typeset as m/s, and not ms^{-1} as I would like (and as is the standard behaviour of siunitx). I've tried setting the unit code to \si[per-mode=reciprocal]{#1}, but it doesn't change the output. If I supply the unit using x unit=\si{\metre\per\second}, I get the desired output, but I would rather not call the \si command explicitly, as in my eyes it would defeat the purpose of using the units library if I end up typesetting the units myself anyway.

MWE:

\documentclass{article}
\usepackage{siunitx}
\usepackage{pgfplots}
\usepgfplotslibrary{units}
\pgfplotsset{unit code/.code={\si{#1}}}
\begin{document}
\begin{tikzpicture}
\begin{axis}[
x unit=\metre\per\second, %This doesn't do what I want
y unit=\si{\metre\per\second}] %This does, but I don't want to type "\si{...}"
\end{axis}
\end{tikzpicture}
\end{document}

enter image description here

Best Answer

It seems that pgfplots passes some additional information through, rather than just the units themselves. This confuses siunitx, which is looking for a unit only (otherwise it bails out of processing the units). So we need to strip out the additional information. As mentioned in pgfplots - x unit with siunitx, we also need to expand the information passed through as it is 'stored' by pgfplots:

\documentclass{article}
\usepackage{siunitx}
\usepackage{pgfplots}
\usepgfplotslibrary{units}
 \makeatletter
\pgfplotsset{
  unit code/.code 2 args=
    \begingroup
    \protected@edef\x{\endgroup\si{#2}}\x
} 
\makeatother
\begin{document}
\begin{tikzpicture}
\begin{axis}[
x unit=\metre\per\second, %This doesn't do what I want
y unit=\si{\metre\per\second}] %This does, but I don't want to type "\si{...}"
\end{axis}
\end{tikzpicture}
\end{document}

(The .code 2 args here is used to throw away the first piece of information in #1, which is where the main issue is.)


For those who would know everything, the full story. If you take a peek at what #1 in the above actually is, you get {}{\pgfkeysvalueof {/pgfplots/x\space unit}}. Now, the problem is the braces. The first stage of siunitx processing is to \edef everything, which leads to {}{\metre\per\second} (\pgfkeysvalueof is expandable and the unit macros are protected). The second step siunitx does is to look for anything that is not a unit macro, which I call 'literal' input. That leads to {}{}, which is not empty and so looks like literal input. So the first 'argument' in the solution throws away the first set of braces, and unbraces the second argument. That means that the value then contains no 'literal' items, and is processed by siunitx using the unit processor as desired.

Life gets a bit more complicated if there is a literal part to the unit, as in some circumstances the expansion process might take place at the 'wrong' time. That happens for example if there is a need to search-and-replace . in a literal unit. Forcing the expansion 'up front' deals with that too.