Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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:

enter image description here

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
            },
    ]
      \addplot[
        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};
      \addplot[
        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};
      \addplot[
        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};
      \addplot[
        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};
      \addplot[
        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}
share|improve this question
add comment

2 Answers

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()}%
   \foreach \c in {#1} {\directlua{addColor("\c")}}
}

% 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:

Result

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

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

function readDataFile(filename)
    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

function addColor(c)
    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 data = readDataFile(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);",
               1.1*barwidth, -#data*spread*barwidth))
    -- 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};",
              -#data*spread*barwidth))
    tex.print(string.format("\\coordinate (min) at (%f,%f);",
                  min+0.0, -#data*spread*barwidth))

    -- 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);",
                  xpos, ypos, spread*aux*barwidth))
        aux =   1
        ypos = ypos - spread*barwidth
        xpos = xpos + 1.0 * p
        color = color + 1
        if (color > #colors) then color = 1; end
    end
end
share|improve this answer
    
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
add comment

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:

Graph

share|improve this answer
    
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
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.