Stacked bar plots with several descriptive nodes

I have a stacked bar plot where I would like to display additional information for each bar—besides their cumulative values. This information should include the non-cumulative values and the corresponding percentage on the left side of each bar. The cumulative values should be displayed on right side of the bars, as follows:

\documentclass{scrartcl}
\usepackage{pgfplots}
\usepackage{pgfplotstable}

\pgfplotsset{compat=1.6}

\begin{document}

Country     Accepted Rejected
US          180      100
Germany     70       40
UK          10       5
}\data

\begin{tikzpicture}
\begin{axis}[ybar stacked, enlargelimits=0.25, symbolic x coords={US, Germany,
UK}, nodes near coords, nodes near coords align=horizontal,xtick=data,
every node near coord/.append style={xshift=5pt}]
\end{axis}
\end{tikzpicture}

\end{document}


What is the appropriate way to do this?

-
This would be much better with the pictures of before and what you want (maybe with the help of Paint). –  m0nhawk Dec 17 '12 at 17:53

This example doesn't work for an arbitrary number of plots, but can quite easily be adjusted for other cases. Maybe some of the wizards know how to change it in a way that it works automatically.

Currently, due to rounding the numbers to do not necessarily add up to 100% like in the first column (18%+59%+24%=101%). But this is something that is quite tricky to solve, e.g. Microsoft Excel suffers from the same problem, afaik.

MWE

\documentclass{scrartcl}
\usepackage{pgfplots}
\usepackage{pgfplotstable}

\begin{document}

Country     Accepted Rejected Pending
US          40       100      30
Germany     70       40       30
UK          50       65       30
}{\data}

\pgfplotstablecreatecol[
create col/expr={
\thisrow{Accepted} + \thisrow{Rejected} + \thisrow{Pending}
}
]{Sum}{\data}

\pgfplotstablecreatecol[
create col/assign/.code={%
\pgfkeys{/pgf/fpu=true,/pgf/fpu/output format=fixed}
\pgfmathparse{\thisrow{Accepted} / \thisrow{Sum} * 100}
\pgfkeyslet{/pgfplots/table/create col/next content}\pgfmathresult
\pgfkeys{/pgf/fpu=false}
}
]{AcceptedPercentage}{\data}

\pgfplotstablecreatecol[
create col/assign/.code={%
\pgfkeys{/pgf/fpu=true,/pgf/fpu/output format=fixed}
\pgfmathparse{\thisrow{Rejected} / \thisrow{Sum} * 100}
\pgfkeyslet{/pgfplots/table/create col/next content}\pgfmathresult
\pgfkeys{/pgf/fpu=false}
}
]{RejectedPercentage}{\data}

\pgfplotstablecreatecol[
create col/assign/.code={%
\pgfkeys{/pgf/fpu=true,/pgf/fpu/output format=fixed}
\pgfmathparse{\thisrow{Accepted} + \thisrow{Rejected}}
\pgfkeyslet{/pgfplots/table/create col/next content}\pgfmathresult
\pgfkeys{/pgf/fpu=false}
}
]{RejectedSum}{\data}

\pgfplotstablecreatecol[
create col/assign/.code={%
\pgfkeys{/pgf/fpu=true,/pgf/fpu/output format=fixed}
\pgfmathparse{\thisrow{Pending} / \thisrow{Sum} * 100}
\pgfkeyslet{/pgfplots/table/create col/next content}\pgfmathresult
\pgfkeys{/pgf/fpu=false}
}
]{PendingPercentage}{\data}

\pgfplotstablecreatecol[
create col/assign/.code={%
\pgfkeys{/pgf/fpu=true,/pgf/fpu/output format=fixed}
\pgfmathparse{\thisrow{Accepted} + \thisrow{Rejected} + \thisrow{Pending}}
\pgfkeyslet{/pgfplots/table/create col/next content}\pgfmathresult
\pgfkeys{/pgf/fpu=false}
}
]{PendingSum}{\data}

% Save table
\pgfplotstablesave[columns/Country/.style={string type}, columns/Sum/.style={numeric as string type}, col sep=comma, disable rowcol styles=false]{\data}{temptable.txt}

\makeatletter
\begin{tikzpicture}
\begin{axis}[
ymin=0,
width=0.7\textwidth,
enlarge x limits=0.30,
symbolic x coords={US, Germany, UK},
xtick=data,
point meta=explicit,
calculate offset/.code={
\pgfkeys{/pgf/fpu=true,/pgf/fpu/output format=fixed}
\pgfmathsetmacro\testmacro{(\pgfplotspointmetatransformed)/1000)}
\pgfkeys{/pgf/fpu=false}
},
every node near coord/.style={
/pgfplots/calculate offset,
yshift=-\testmacro,
xshift=-5pt,
font=\tiny
},
nodes near coords align=center,
ybar stacked, nodes near coords={\hspace{-.5cm} \pgfmathprintnumber[precision=0]{\pgfplotspointmeta} (\pgfmathprintnumber[precision=0]{\labela}\%)  \hspace{.4cm} \pgfmathprintnumber[precision=0]{\labelb}},
]

visualization depends on={\thisrow{AcceptedPercentage} \as \labela},
visualization depends on={\thisrow{Accepted} \as \labelb},
] table[y=Accepted, meta=Accepted, col sep=comma] {temptable.txt};
visualization depends on={\thisrow{RejectedPercentage} \as \labela},
visualization depends on={\thisrow{RejectedSum} \as \labelb},
] table[y=Rejected, meta=Rejected, col sep=comma] {temptable.txt};
visualization depends on={\thisrow{PendingPercentage} \as \labela},
visualization depends on={\thisrow{PendingSum} \as \labelb},
] table[y=Pending, meta=Pending, col sep=comma] {temptable.txt};

\end{axis}
\end{tikzpicture}
\makeatother
\end{document}

-

Summary:

This solution uses a combination of knitr and R. Knitr provides (amongst many things) a methodology to access the R platform from inside LaTeX, at point of typeset. This opens up opportunities of Biblical Proportions, however, in relation to this thread, being given access to R means that ggplot2 (being a superior plotting package) can be used to generate plots, which are in-turn embedded directly into the respective LaTeX document.

For those of you involved with technical papers, dissertations, theses' and the like, you may instantly recognize the benefits of being able to create your plots on the fly, at point of typeset, since you are given the convenience and peace of mind in knowing that each and every plot is current and up-to-date.

Incidentally, there is a package in R (xtable) which allows you to call data in the format recognized by LaTeX (ie LaTeX Table). This means that using knitr and R (via the xtable and RODBC packages) one can create data-driven tables from external ODBC sources in LaTeX.

There is also the subject of embedding equations and bibtex citations within the ggplot2 objects, both of which are no problem with the tikzDevice. Perhaps you are writing two papers, Paper X and Paper Y, and you wish to use the exact same plot in Paper X and Paper Y, but Paper X and Paper Y have a different set of references. Furthermore, this particular plot represents a literature-survey of prior research, and, therefore you need cite on the plot, in the same numbering sequence as per the Paper X and Paper Y bibliography. In essence, reference '[10]' in the Paper X plot may end up being reference '[11]' in the Paper Y plot, even though they both refer to the same source. Knitr makes these important differences a triviality.

At this point, upon reflection, reverting to MWord seems tantamount to starting fire with flint when lighters are available. Of course, Bear Grylls can start fire with far less, including a plastic back full of yellow liquid, however, he has a safety crew.... Getting back on track, in this solution, R has been called DIRECTLY from within the LaTeX document. The R code blocks are held in several (what is referred to as) 'Chunks'. These chunks can be identified as being the code held between the <<>>= and @ flags. Chunks can call other chunks. Meaning that the same chunk can be called multiple times inside the same document. Drilling down to each chunk, options parsed inside << and >> are knitr specific options, that give control over the functioning of the chunk. One of my other posts, Random Watermark Placement has gone through, in greater detail, the basic principles that have been used to solve this task. Knitr can be found here: Knitr Homepage, Yihui, the author, should be given a medal. Result: Please find immediately below, an excerpt (page 4) from the Solution Document. Working Example. Please find below, entire code to produce the Solution Document. Very Basic Preamble.... \documentclass[a4paper,9pt,hidelinks]{article} \usepackage[inner=2.0cm,outer=2.0cm,top=4cm,bottom=3cm,marginparwidth=1.75cm,marginparsep=2mm]{geometry} %opening \title{Sample Stacked Bar} \author{ADP}  Run Chunk to Define Settings, including message suppression and minimum packages required in R. %Define the Settings <<setup_packages,eval=FALSE,echo=FALSE>>= ##Install latest version of tikzDevice (Installation Currently Disabled) ##install.packages("tikzDevice", repos="http://R-Forge.R-project.org") ##Suppress Messages suppressMessages( library(ggplot2)) #Dont Want spam-like messages. suppressMessages( library(reshape)) suppressMessages( library(grid)) suppressMessages( library(tikzDevice)) suppressMessages( library(knitr)) ##Load Packages Quietly require(ggplot2,quiet=TRUE) require(reshape,quiet=TRUE) require(grid,quiet=TRUE) require(tikzDevice,quiet=TRUE) require(knitr,quiet=TRUE) @  Run chunk to define the theme. This will remain in effect across multiple plots, encouraging consistency within the entire document. <<setup_theme,eval=FALSE,echo=FALSE>>= ##Set the Theme theme_new <- theme_set(theme_bw(10)) col_bg <- "grey95" col_axis <- "grey20" theme_new <- theme_update( plot.title = element_text(lineheight = 2, angle = 0,size = 12,colour = col_axis), axis.title.x = element_text(angle = 0,size = 10, colour = col_axis), axis.title.y = element_text(angle = 90,size=10, colour = col_axis), axis.text.x = element_text(colour =col_axis,size=8,angle=0,hjust=0.5, vjust=0,face="plain"), axis.text.y = element_text(colour =col_axis, size=8,angle=0, hjust=1, vjust=0.5,face="plain"), axis.ticks = element_line( colour=col_axis), axis.ticks.x = element_line(colour=col_axis), axis.ticks.y = element_line(colour=col_axis), legend.position = c(0,1), legend.direction='vertical', legend.box='horizontal', legend.justification = c(0, 1), legend.text = element_text(size = 8), legend.title = element_text(size = 9), legend.text.align =0, legend.background = element_rect(fill = 'white',colour = "gray", size = 0.1), legend.key = element_rect(colour = 'white', fill = 'white', size = 0, linetype='solid'), legend.key.height = unit(0.3,"cm"), panel.background = element_rect(fill=col_bg)) @  This is the chunk which defines global chunk settings from this point forward. <<chunk_settings,eval=FALSE,echo=FALSE>>= ## Default Chunk Settings, For Returned Graphics. opts_chunkset(fig.width = 6)
opts_chunk$set(fig.height = 5) opts_chunk$set(fig.align = 'center')
opts_chunk$set(fig.path = 'Images_Knitr/') opts_chunk$set(fig.keep = 'all')
opts_chunk$set(dev='tikz') opts_chunk$set(external = FALSE)
opts_chunk\$set(size = 'small')
@


The next two chunks generate and melt the data prior to plotting.

<<process_data,echo=FALSE,eval=FALSE>>=
#Assemble Data.
country <- c("USA","Germany","United Kingdom")
accept <- c(40,70,50)
reject <- c(100,40,65)
pend <- c(30,30,30)
tot <- accept + reject + pend
data.in <- data.frame(Country=country,Rejected=reject,Accepted=accept,Pending=pend,Total=tot)
rm(country,accept,reject,pend,tot)

#Determine Cumulative Values.
data.in.cum <- data.in[,-5]
for(c in 3:ncol(data.in.cum)){
data.in.cum[,c] <- data.in.cum[,c] + data.in.cum[,c-1]
}
rm(c)
@

<<melt_data,echo=FALSE,eval=FALSE>>=
#Melt Data into data frame
data.melt <- data.frame(melt(data.in,id=c("Country","Total"))) #need
data.melt.cum <- data.frame(melt(data.in.cum,id=c("Country")))

##Set Arrays of Mis Values used in the Plot.
data.total <- data.melt[,2]
data.cum <- data.melt.cum[,3]
data.melt <- data.melt[,-2]
data.pcnt <- round(100*data.melt[,3]/data.total,0)
colnames(data.melt) <- c("Country","Status","Count")
@


The Final Chunk is the actual chunk that creates the plot.

<<plot_data,echo=FALSE,eval=FALSE,dev='tikz'>>=
##This is a bit of a hack to split the labels either side of the bars
##Otherwise, two geom_text layers would need to be used.
spc <- "                     "

##Create the Final Plot.
ggplot(data.melt,aes(x=Country,y=Count,fill=Status,ymax=-1)) +
geom_bar(width = 0.3,color="black") +
geom_text(aes(label = paste(Count," (",data.pcnt,"%)",spc,data.cum,sep="")),position="stack",size = 3,  hjust = 0.6, vjust = 3) +
scale_y_continuous(name="Amount") +
scale_x_discrete(name="Country") +
ggtitle("Acceptance Statistics for Various Countries")
@


The remaining code should be familiar as showing commonalities to a standard document, with the above chunks being called at the appropriate locations...

\begin{document}
\maketitle  #make titlepage.
\begin{abstract}
In this sample, the R packages ggplot2 is used to plot a simple stacked bar, demonstrating use of labels. This is called directly from inside \LaTeX document using the knitr package.
\end{abstract}
Call the chunk that loads packages that are required for this excercise.
<<setup_packages,eval=TRUE,echo=TRUE>>=
@

Defines the formatting for charts. This formatting will hold for all subsequent charts, providing a 'template' for document consistency. Each chart, of course can be modified on a case by case basis. The theme set function is as per ggplot2 package.
<<setup_theme,eval=TRUE,echo=TRUE>>=
@

\section{Set Default Chunk Settings}
Some default chunk settings can be set, to create consistent environment. Note the image size variables which are reflected in the size of the plots.
<<chunk_settings,eval=TRUE,echo=TRUE>>=
@

\section{Process the Data}
We need to now process the data, determining cumulative values etc...
<<process_data,echo=TRUE,eval=TRUE>>=
@

We need to now melt the data, so that it is in a format which can be called by the ggplot2 class, taking advantage of aesthetics.

<<melt_data,eval=TRUE,echo=TRUE>>=
@

\newpage
\section{Create the Plot}
Finally, we can plot the object,using the tikzdevice to insert directly into the document. This chunk is called with the eval=FALSE, echo = TRUE flags, which returns console messages, but not the plot.
<<plot_data,echo=TRUE,eval=FALSE,dev='png',dpi=1000>>=
@

However, the same chunk can be executed to return the plot only and  none of the R message or used code by setting echo = FALSE and eval = TRUE.

\begin{figure}[b!]
<<plot_data,echo=FALSE,eval=TRUE,dev='png',dpi=1000>>=
@
\caption{The Resulting Stacked Bar Chart.}
\end{figure}

\end{document}

-
As a relatively new R enthusiast, I find this very informative and indeed useful. Thanks! –  Harold Cavendish Dec 23 '12 at 14:44
I was totally blown away when I first discovered what could be done using R and LaTeX together. –  Nicholas Hamilton Dec 23 '12 at 14:46