PGFPlots Bar Charts – Fix Horizontal Bar-Charts Alignment

bar chartchartspgfplotstikz-pgf

I'm drawing a rather unusual bar-chart to display the results of a small survey. The axis of the chart extends in both the negative and the positive domain. However, there is no zero and all values are positive. That sounds rather complicated, so here's a picture:

TextA

It would be nice to see the actual values. Section 4.4.4 of the PGFPlots-manual advises to use nodes near coords, nodes near coords align = {horizontal} in those cases. Here's the result:

TextB

This looks nice, but the values are obviously wrong. To trick the bar-chart into forgetting that there's actually no zero-value present, I change the x-ticks in the plot-style and subtract 1. The displayed value next to the bars is therefore the coordinate for PGFPlots and the only one I input.

Now, there are two options: calculating it during compilation as |x|+1 (first picture) or adding the correct value explicitly (second picture):

TextC
TextD

Both options mess up the layout in different ways and I could not figure out how to position the nodes correctly. Here's the code of the MWE, which generates the shown graphs:

\documentclass[11pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{pgfplots}
\usepackage{floatrow}
\usepackage{subfigure}

\pgfplotsset{TestBar/.style={% Overall settings
    width = 0.75\textwidth,
    line width=1pt,
    tick style={line width=0.8pt},
    xmin = -8, xmax = 8,
    xtick       = {-8,-6,-4,-2, 0, 2, 4, 6, 8},
    xticklabels = { 9, 7, 5, 3, 1, 3, 5, 7, 9},
    xmajorgrids = true,
    minor x tick num = 1,
    tick align = outside,
    xtick pos = left, ytick pos = left,
    xbar,
    height = 3cm,
    enlarge y limits = 0.5, ytick = {1,2},
    yticklabels = {C2, C1}}
}

\pgfplotsset{TestBarY/.style={% Draw y-Axis without anything
    width = 0.75\textwidth,
    height = 3cm,
    line width = 1pt,
    xmin = -8, xmax = 8,
    xmajorgrids = false,
    xtick = \empty, ytick = \empty,
    no markers,
    minor x tick num = 1,
    axis y line* = center}
}

\pgfplotsset{TextA/.style={}}% Show no values

\pgfplotsset{TextB/.style={% Show value as given in the coordinates.
    nodes near coords,
    nodes near coords align = {horizontal}}
}

\pgfplotsset{TextC/.style={% Calculate value in point-meta.
    nodes near coords,
    nodes near coords align={horizontal},
    point meta = {abs(x)+1}}
}

\pgfplotsset{TextD/.style={% Caption explicitly added in point-meta.
    nodes near coords,
    nodes near coords align={horizontal},
    point meta = explicit symbolic}
}

\begin{document}

\begin{figure}[htb]
\subfigure[TextA: no text.]{
\begin{tikzpicture}
    \begin{axis}[TestBar]
        \addplot[TextA] coordinates { (2.555,1) (-1.279,2) };
    \end{axis}

    \begin{axis}[TestBarY]\addplot coordinates {(0,0)};\end{axis}
\end{tikzpicture}}\
\subfigure[TextB: node-text from coordinates.]{
\begin{tikzpicture}
    \begin{axis}[TestBar]
        \addplot[TextB] coordinates { (2.555,1) (-1.279,2) };
    \end{axis}

    \begin{axis}[TestBarY]\addplot coordinates {(0,0)};\end{axis}
\end{tikzpicture}}\
\subfigure[TextC: node-text calculated in \emph{point meta} as abs(x)+1.]{
\begin{tikzpicture}
    \begin{axis}[TestBar]
        \addplot[TextC] coordinates { (2.555,1) (-1.279,2) };
    \end{axis}

    \begin{axis}[TestBarY]\addplot coordinates {(0,0)};\end{axis}
\end{tikzpicture}}\
\subfigure[TextD: node-text explicitly added to \emph{point meta}.]{
\begin{tikzpicture}
    \begin{axis}[TestBar]
        \addplot[TextD] coordinates { (2.555,1) [3.56] (-1.279,2) [2.28] };
    \end{axis}

    \begin{axis}[TestBarY]\addplot coordinates {(0,0)};\end{axis}
\end{tikzpicture}}
\end{figure}

\end{document}

Best Answer

The horizontal alignment option determines whether to place the label on the left or the right side of the bar by looking at the sign of the meta value. By converting it to an absolute value, all the labels end up on the right hand side. One way to achieve what you're trying to do is to only convert the meta value when printing it in the label. You can do that using

nodes near coords={
    \pgfkeys{/pgf/fpu=true}
    \pgfmathparse{abs(\pgfplotspointmeta)+1}
    \pgfmathprintnumber{\pgfmathresult}
}

Switching on the fpu library first is necessary because the meta value has already been converted to a floating point representation. You can then do your calculations, and finally print the number using \pgfmathprintnumber.

Bonus bit: For drawing a line at x=0, you don't have to use a whole new axis. The key

before end axis/.code={
        \draw ({rel axis cs:0,0}-|{axis cs:0,0}) -- ({rel axis cs:0,1}-|{axis cs:0,0});
    }

will draw a line that starts at the bottom of the plot at x=0 and goes to the top, regardless of your axis limits.

Here's the full code:

\documentclass[11pt]{article}
\usepackage{pgfplots}

\pgfplotsset{
    TestBar/.style={% Overall settings
        width = 0.75\textwidth,
        line width=1pt,
        tick style={line width=0.8pt},
        xmin = -8, xmax = 8,
        xtick       = {-8,-6,-4,-2, 0, 2, 4, 6, 8},
        xticklabels = { 9, 7, 5, 3, 1, 3, 5, 7, 9},
        xmajorgrids = true,
        minor x tick num = 1,
        tick align = outside,
        xtick pos = left, ytick pos = left,
        xbar,
        height = 3cm,
        enlarge y limits = 0.5, ytick = {1,2},
        yticklabels = {C2, C1},
        nodes near coords={
            \pgfkeys{/pgf/fpu=true}%
            \pgfmathparse{abs(\pgfplotspointmeta)+1}%
            \pgfmathprintnumber{\pgfmathresult}
        },
        nodes near coords align = {horizontal},
        before end axis/.code={
            \draw ({rel axis cs:0,0}-|{axis cs:0,0}) -- ({rel axis cs:0,1}-|{axis cs:0,0});
        }
    }
}




\begin{document}

\begin{tikzpicture}
    \begin{axis}[TestBar]
        \addplot [] coordinates { (2.555,1) (-1.279,2) };
    \end{axis}
\end{tikzpicture}

\end{document}
Related Question