# Horizontal waterfall chart with labels for each bar

I've looked through similar questions here and here, but I'm still stuck. Basically what I'm trying to achieve is this plus bar labels:

I have two problems:

1) My code sucks. Right now the y-axis labels and the waterfall connectors are placed manually, which is suboptimal since I'll be making 15 or so versions of the chart.

2) I can't get the bar labels to show up in the right place. I've used Jake's Centered Nodes Near Coords solution before (can't find the reference stack exchange question at the moment), but for some reason they refuse to go in the proper place in this example.

Any suggestions on how to improve this / make the labels work would be greatly appreciated.

[EDIT] I really need an answer using PGFplots, please. This needs a quick turnaround and I don't have time to learn a different .cvs to latex implementation.

MWE:

\documentclass{article}
\usepackage{pgfplots, pgfplotstable}
\usepackage{filecontents}

\pgfdeclareplotmark{waterfall bridge}{\pgfpathmoveto{\pgfpoint{0pt}{33pt}}\pgfpathlineto{\pgfpoint{0pt}{9pt}}\pgfusepathqstroke}
\pgfdeclareplotmark{waterfall bridge 2}{\pgfpathmoveto{\pgfpoint{0pt}{19pt}}\pgfpathlineto{\pgfpoint{0pt}{-5pt}}\pgfusepathqstroke}
\pgfdeclareplotmark{waterfall bridge 3}{\pgfpathmoveto{\pgfpoint{0pt}{5pt}}\pgfpathlineto{\pgfpoint{0pt}{-19pt}}\pgfusepathqstroke}
\pgfdeclareplotmark{waterfall bridge 4}{\pgfpathmoveto{\pgfpoint{0pt}{-9pt}}\pgfpathlineto{\pgfpoint{0pt}{-33pt}}\pgfusepathqstroke}
%~~ALL OF THE MARK POSITIONING IS UGLY~~
% I would like them to use relative, rather than absolute, positioning

\begin{filecontents}{datatable.csv}
2 3 2 -6 -3
\end{filecontents}

\makeatletter
\pgfplotsset{
calculate xoffset/.code={
\pgfkeys{/pgf/fpu=true,/pgf/fpu/output format=fixed}
\pgfmathsetmacro\labelshift{(\pgfplotspointmeta*10^\pgfplots@data@scale@trafo@EXPONENT@x)/2)*\pgfplots@x@veclength)}
\pgfkeys{/pgf/fpu=false}
},
nodes near coords horizontally centered/.style={
every node near coord/.append style={
/pgfplots/calculate xoffset,
xshift=-\labelshift,yshift=-.25pt
},
nodes near coords align=center
}
}
\makeatother

\begin{document}
\begin{tikzpicture}
\begin{axis}[
xbar stacked,
point meta=explicit,
nodes near coords horizontally centered, %~~THIS ISN'T WORKING~~
bar width=10pt,
axis x line*=bottom,
axis on top=true,
ytick style={opacity=0},
yticklabel style={font=\small, text width=2cm, align=center},
x axis line style={opacity=0},
y axis line style={opacity=0},
y dir=reverse,
ytick={-.35,-.175,0,.175,.35}, %~~THIS IS UGLY!~~
yticklabels={
label1,label2,label3,label4,label5
},
]
fill=cyan,
draw=none,
bar shift=28pt,
mark options={
lightgray,
thick,
},
mark=waterfall bridge,
] table [y expr=\coordindex, x index=0, meta index=0] {datatable.csv};
fill=orange,
draw=none,
bar shift=14pt,
mark options={
lightgray,
thick,
},
mark=waterfall bridge 2,
] table [y expr=\coordindex, x index=1, meta index=1] {datatable.csv};
fill=blue,
draw=none,
bar shift=0pt,
mark options={
lightgray,
thick,
},
mark=waterfall bridge 3,
] table [y expr=\coordindex, x index=2, meta index=2] {datatable.csv};
fill=red,
draw=none,
bar shift=-14pt,
mark options={
lightgray,
thick,
},
mark=waterfall bridge 4,
] table [y expr=\coordindex, x index=3, meta index=3] {datatable.csv};
fill=yellow,
draw=none,
bar shift=-28pt,
] table [y expr=\coordindex, x index=4, meta index=4] {datatable.csv};
\draw[ultra thin,lightgray] (axis cs:0,\pgfkeysvalueof{/pgfplots/ymin}) -- (axis cs:0,\pgfkeysvalueof{/pgfplots/ymax}); % adds x=0 line
\end{axis}
\end{tikzpicture}

\end{document}

-

Well, just for fun (and to better learn new tools) I did an implementation in lua, which can be run through LuaLatex.

The lua code reads from an external .cvs, in which data is expected to be in different lines (a number per line), and generates a tikz graphic which self-adapts its axis to the data read. Also, the generated tikz defines a set of coordinates named row1, row2 and so on located a the center of each bar, and a coordinate named min located at the x position of the leftmost bar. These coordinates can be used to put labels in the diagram, either centered on the bars or at the left of the graphic.

The colors used in the graphic are user-definable. If there are less colors than bars, they are cycled.

This is the LaTeX code:

\documentclass{book}
\usepackage{xcolor}
\usepackage{tikz}
\usepackage{filecontents}
\directlua{dofile("luaFunctions.lua")}

%create a pair of datafiles
\begin{filecontents*}{datafile1.csv}
2
3
2
-6
-3
\end{filecontents*}
\begin{filecontents*}{datafile2.csv}
3
4
2
3
-4
-6
2
-5
\end{filecontents*}

% latex commands to execute the lua functions
\def\waterfallChart#1{\directlua{waterfallChart("#1")}}
\def\setColors#1{%
\directlua{emptyColors()}%
}

% set some styles
\tikzset{bar connection/.style = {black!50, thick}}
\setColors{cyan!80!black!50, orange, blue!80!black, red, yellow}

\begin{document}
\begin{tikzpicture} % First graph
\waterfallChart{datafile1.csv}  % This draws the chart
% Now, adding labels, centered at each bar
\foreach \label [count=\n from 1] in {foo, bar, bad, foobar, spam}
\node at (row\n) {\label};
\end{tikzpicture}

\vskip 2cm

\begin{tikzpicture} % Second graph
\setColors{brown,red,orange}   % Different colors for this one
\waterfallChart{datafile2.csv} % Draw the chart
% Put labels (at the left of the figure in this case)
\foreach \label [count=\n from 1] in {foo, bar, bad, foobar, spam, eggs, lorem, ipsum}
\node[left] at (row\n-|min) {\label};
\end{tikzpicture}
\end{document}


This is the result:

And this is the content of the file luaFunctions.lua:

colors = {"blue","green"}  -- Default colors

local input = io.open(filename, 'r')
local dataTable = {}
local n
for line in input:lines() do
table.insert(dataTable, line)
end
input:close()
return dataTable
end

function emptyColors()
colors = {}
end

table.insert(colors,c)
end

function computeExtremes(dataTable)
local max, min, x
x = 0.0
min = 0.0
max = 0.0
for i,p in ipairs(dataTable) do
x = x + p
if (x<min) then min = x end
if (x>max) then max = x end
end
return min, max
end

function waterfallChart(filename)
local min, max, n_steps, step, color, xpos, ypos, barwidth, aux, spread

-- Configure here as required
barwidth = 0.5  -- Height of each bar in the chart
spread = 1.4    -- Distance among baselines of the bars (in barwidth units)
n_ticks = 6     -- Number of ticks in the x-axis

min, max = computeExtremes(data)
step = (max-min)/n_ticks
max = min + n_ticks*step
xpos = 0.0
ypos = 0.0
aux = 0
-- Draw axes
-- Vertical axis
tex.print(string.format("\\draw (0,%f) -- (0, %f);",
-- Horizontal axis
tex.print(string.format("\\foreach \\tick in {%d, %d, ..., %d}",
min, min+step, max))
tex.print(string.format("  \\draw (\\tick, %f) -- +(0, -2mm) node[below] {\\tick};",
tex.print(string.format("\\coordinate (min) at (%f,%f);",

-- Draw the bars
color = 1
for i,p in ipairs(data) do
tex.print(string.format("\\fill[%s] (%f,%f) rectangle +(%f, %f) coordinate[midway] (row%d);",
colors[color], xpos, ypos, p+0.0, barwidth, i))
tex.print(string.format("\\draw[bar connection] (%f, %f) -- +(0,%f);",
aux =   1
xpos = xpos + 1.0 * p
color = color + 1
if (color > #colors) then color = 1; end
end
end

-
You need another answer using pgfgantt package too.:) –  percusse Mar 2 '13 at 18:59
It works, but I really need an answer using PGFplots. Don't have time to try to learn a new package, this has a quick turnaround. –  Trevor Mar 4 '13 at 18:40
How can this ever be downvoted? –  percusse Mar 4 '13 at 20:09
@percusse In fact, both my two answers were downvoted :( –  JLDiaz Mar 5 '13 at 8:37

I don't know about pgfplots nor pgfplotstable, so I can't understand your code. But looking at your graphic I wrote a pure tikz solution, which perhaps could be simpler. The main drawback is that you have to specify your data points a part of a \foreach loop (instead of reading them from a .csv) and also specify the colors of the bars. You'll decide if this can be useful to you.

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}
\begin{document}
\begin{tikzpicture}
\xdef\myypos{0}
\xdef\myxpos{0}
\xdef\mywidth{0.5}
\xdef\myyprev{0.0}
% Axes (ugly, some hardcoded numbers)
\draw (0,1.1*\mywidth) -- (0, -5*2.2*\mywidth);
\foreach \tick in {-2,0,...,8}
\draw (\tick, -5*2.2*\mywidth) -- +(0,-2mm) node[below] {\tick};

% bars and labels
\foreach \mylen/\mylab/\mycolor in
{2/label1/cyan!80!black!50,
3/label2/orange,
2/label3/blue!80!black,
-6/label4/red,
-3/label5/yellow}
{
\fill[\mycolor] (\myxpos,\myypos) rectangle +(\mylen, \mywidth) node[midway,black] {\mylab};
\draw[black!40,thick] (\myxpos, \myypos) -- +(0, 2.2*\myyprev*\mywidth);
\pgfmathparse{\myypos - 1.2*\mywidth}\xdef\myypos{\pgfmathresult}
\pgfmathparse{\myxpos + \mylen}\xdef\myxpos{\pgfmathresult}
\xdef\myyprev{1.0}
}
\end{tikzpicture}
\end{document}


Result:

-
Not a bad solution in general, but for my use case I'm going to need to use .csv files. I'm going to be making a bunch of these and each will have many data points. –  Trevor Mar 2 '13 at 5:19
@Trevor Perhaps you can combine my approach with the datatool package, or use lualatex for reading and iterating (and computing the final size of the axes, etc) –  JLDiaz Mar 2 '13 at 13:29