[Tex/LaTex] How to make a pgfplots stacked xbar plot with bar-“explicit meta” labels computed from input table data

bar chartlabelsnodespgfplots

I am making a pgfplots stacked xbar plot where each bar has a distinct
label, in addition to a numeric bar length. Each pair of bars also
has a common label. I would like to mark each bar with its distinct
label and its length, and mark each pair of bars with their common
label.

Two parts to my question (EDIT: (1) is answered in 3):

  1. how to avoid defining the extra Score-name column (see MWE
    below), and instead compute it from the Score and Name
    columns in the table? EDIT: according to the package author
    (answer to 3), there is no built-in support for this, but
    it's on his TODO list. EDIT 2013-3-25: according to answer below, this is now supported.

  2. how to make the computed labels appear on the right y axis,
    instead of at the end of the bars?

I don't know the best way to depict the data, but per bar extra y
tick
labels, as in Jake's answer to another question would
probably be good.

I couldn't figure out how to get per bar extra y tick labels,
but I have alternate solution where the label placement is not as
good: add an extra column to the input data table and use it for the
per-bar labels. Problems with this solution include: having to add
the extra column; not being able to use the pgfplots command to format
the numeric part of the labels from the extra column (illustrated
below); and (subjectively) that it doesn't look as good as extra u
tick
labels would.

My MWE (also based on Jake's answer to another question. Thanks Jake!) has two
figures. The first figure shows my solution by manually adding the
Score-name column, and the second figure shows how the numeric
formatting directives (i.e. scientific notation) are not being honored
in that first figure (which seems natural, because Score-name is
not numeric).

\documentclass{article}
\usepackage{pgfplots}
\usepackage{filecontents}
\pgfplotsset{compat=newest}

\begin{filecontents}{data1.dat}
Age-interval  Y-Position  Score   Name  Score-name
20-30         1           0.15    Peter {0.15 (Peter)}
30-40         2           0.20    Jeff  {0.20 (Jeff)}
40-50         3           0.12    Steve {0.12 (Steve)}
50-60         4           1.24    John  {0.24 (John)}
\end{filecontents}

\begin{filecontents}{data2.dat}
Age-interval  Y-Position  Score    Name    Score-name
20-30         1           0.159    Peeteer {0.159 (Peeteer)}
30-40         2           0.209    Jeeff   {0.209 (Jeeff)}
40-50         3           0.129    Steevee {0.129 (Steevee)}
50-60         4           1.249    Joohn   {0.249 (Joohn)}
\end{filecontents}


\begin{document}

\begin{tikzpicture}
\begin{axis}[
    xlabel={The \texttt{format/sci} is ignored below, but the \texttt{font=\textbackslash{}small} is obeyed below:},
    xbar,     
    %bar width=2pt,
    ytick=data,
    width=8 cm,
    height=6 cm,
    enlarge y limits={true, value=0.2},
    xmin=-0.01,
    xmax = 2.0,
    xticklabel pos = upper,
    tick align = outside,
    yticklabel pos=left, 
    yticklabels from table={data.dat}{Age-interval},
    %ylabel={Age intervals (yr)},
    nodes near coords,
    every node near coord/.append style={
      anchor=west,
      font=\small,
      % This format/sci gets ignored, because the Score-name is not a
      % number:
      /pgf/number format/sci,
      %/pgf/number format/zerofill,
      /pgf/number format/precision=2,
    },
    point meta=explicit symbolic
]
\addplot table [
    y=Y-Position,
    x=Score,
    meta=Score-name
] {data1.dat};
\addplot table [
    y=Y-Position,
    x=Score,
    meta=Score-name
] {data2.dat};

\end{axis}
\end{tikzpicture}

\begin{tikzpicture}
\begin{axis}[
    xlabel={Both the \texttt{format/sci} and the \texttt{font=\textbackslash{}small} are obeyed below:},
    xbar,     
    %bar width=2pt,
    ytick=data,
    width=8 cm,
    height=6 cm,
    enlarge y limits={true, value=0.2},
    xmin=-0.01,
    xmax = 2.0,
    xticklabel pos = upper,
    tick align = outside,
    yticklabel pos=left, 
    yticklabels from table={data.dat}{Age-interval},
    %ylabel={Age intervals (yr)},
    nodes near coords,
    every node near coord/.append style={
      anchor=west,
      font=\small,
      % This format/sci gets ignored, because the Score-name is not a
      % number:
      /pgf/number format/sci,
      %/pgf/number format/zerofill,
      /pgf/number format/precision=2,
    },
    % THIS IS UNCOMMENTED ABOVE
    %point meta=explicit symbolic
]
\addplot table [
    y=Y-Position,
    x=Score,
    meta=Score-name
] {data1.dat};
\addplot table [
    y=Y-Position,
    x=Score,
    meta=Score-name
] {data2.dat};

\end{axis}
\end{tikzpicture}


\end{document}

My solution
Illustration of problems

Best Answer

Here is a custom hack which is tailored only to the problem at hand. First we redefine the nodes near coords* for our own purposes and removing many of the necessary checks. Because we are sure that we won't be using them. You have to take it inside the TikZ picture environment to make this local to the picture otherwise all pictures will be affected.

Then, when placing the nodes near coords we fix the bounding box of the local scope with a coordinate, named (O) here. Then we reset the transformation inside that scope locally. This allows us to compute the distance to the right y-axis and we west anchor the node with a little shift to the left.

Regarding the first question you can use visualization depends on= key to harvest data from other tables, format the number printing etc.

There might be an easier way to do the positioning but I just made it on auto pilot. Please use this to show smarter ways of doing it.

Here is the full code:

\documentclass{article}
\usepackage{pgfplots}
\usepackage{filecontents}
\pgfplotsset{compat=1.7}
\usetikzlibrary{calc}

\begin{filecontents}{data1.dat}
Age-interval  Y-Position  Score   Name  
20-30         1           0.15    Peter
30-40         2           0.20    Jeff  
40-50         3           0.12    Steve
50-60         4           1.24    John  
\end{filecontents}
\begin{filecontents}{data2.dat}
Age-interval  Y-Position  Score    Name    
20-30         1           0.159    Peeteer 
30-40         2           0.209    Jeeff   
40-50         3           0.129    Steevee 
50-60         4           1.249    Joohn   
\end{filecontents}

\pgfkeys{/pgfplots/nodes near coords*/.append style={scatter/@post marker code/.code={%
       \begin{scope}
         \coordinate (O) at (0,0);
         \pgftransformreset
         \path let \p1 = ($(axis description cs:1,1)-(O)$) in
          node[/pgfplots/every node near coord,anchor=west] at ([xshift=\x1+1.5mm]O) {#1};
       \end{scope}
       }
    }
}

\begin{document}
\begin{tikzpicture}
\begin{axis}[
    xbar,clip=false,     
    ytick=data,
    width=8 cm,height=6 cm,
    enlarge y limits={true, value=0.2},
    xmin=-0.01,xmax = 2.0,
    xticklabel pos = upper,
    tick align = outside,
    yticklabel pos=left, 
    yticklabels from table={data2.dat}{Age-interval},
    visualization depends on={value \thisrow{Name} \as \labela},
    visualization depends on={value \thisrow{Score} \as \labelb},
    every node near coord/.append style={
      font=\small,
    },
    nodes near coords={\pgfmathprintnumber[sci]{\labelb} (\labela)},
]
\addplot table [
    y=Y-Position,
    x=Score,
] {data1.dat};
\addplot table [
    y=Y-Position,
    x=Score,
] {data2.dat};
\end{axis}
\end{tikzpicture}
\end{document}

enter image description here

Related Question