19

I am trying to generate the following figure more automatically based on this example. Currently, the nodes above and below the bars are generated manually, which I like to avoid. I would prefer to add the text for the upper labels in the datatable and calculate their positions. In the same way, I would like to change the lower values that state the current total.

Also I would like to be able to add another bar in between 4 and 7 that contains the current total (to add emphasis to the current sum like best case, expected case, worst case)

Bonus Question

I tried to use the pgfmathabs function to have only non-negative values for node near coord. This is does not work, therefore I am first multiplying and then taking the square root, which seems rather inappropriate.

enter image description here

In order to better explain the desired outcome, I added a picture. I am not sure if the number belows the "step" bars (e.g. "oh no") are needed, if a "full" bar (e.g. "expected case") follows. If another "step" bars follows, they should be there. So if it would be possible to omit the lower numbers, in front of full bars that would be great. (In this minimal example, this does not really make sense, since all number would be ommited but in my full example with 10 "step" bars and 3 "full" bars its a different story.)

enter image description here

MWE

\documentclass[border=5mm, 10pt]{standalone}
\usepackage{pgfplots, pgfplotstable}
\usepackage{filecontents}

\pgfplotsset{compat=1.8}

\begin{filecontents}{datatable.csv}
16 
 -4  
 -7  
5
\end{filecontents}

\newcommand\barwidthval{22.5pt}%bar width value

\pgfplotstableset{
    create on use/accumyprev/.style={
        create col/expr={\prevrow{0}+\pgfmathaccuma}
    }
}

\makeatletter
\pgfplotsset{
    centered nodes near coords/.style={
    calculate offset/.code={
        \pgfkeys{/pgf/fpu=true,/pgf/fpu/output format=fixed}
        \pgfmathsetmacro\testmacro{(\pgfplotspointmeta*10^\pgfplots@data@scale@trafo@EXPONENT@y)/2*\pgfplots@y@veclength)}
        \pgfkeys{/pgf/fpu=false}
    },
    every node near coord/.style={
        /pgfplots/calculate offset,
        yshift=-\testmacro,
        black,
    },
    nodes near coords align=center
    },  
}

\begin{document}

\begin{tikzpicture}
\begin{axis}[
    no markers,
    axis x line=center,
    ybar stacked,
    ymin=-20,
    point meta=explicit,
    centered nodes near coords, 
    axis lines*=left,
    xtick=data,
    enlarge x limits=0.05,
    enlarge y limits=0.20,
    major tick length=0pt,
    bar width=\barwidthval,
    xticklabels={},
    xticklabel style={text width=2cm, align=center},
    ytick=\empty,
    x axis line style={opacity=0},
    y axis line style={opacity=0},
    ylabel={Values},
    axis on top
]

% 0-x-Axis
\draw ({rel axis cs:0,0}|-{axis cs:0,0}) -- ({rel axis cs:1,0}|-{axis cs:0,0});

% The first plot sets the "baseline": Uses the sum of all previous y values, except for the last bar, where it becomes 0
\addplot +[
    y filter/.code={\ifnum\coordindex>2 \def\pgfmathresult{0}\fi},
    draw=none,
    fill=none
] table [x expr=\coordindex, y=accumyprev] {datatable.csv};

% The values
\addplot +[
    fill=orange,
    draw=orange,
    ybar stacked,
    nodes near coords,
    nodes near coords={
    \pgfkeys{
      /pgf/fpu=true,  % PGFplots uses floating points internally
     }
    % pgfmathabs does not work here   
    \pgfmathparse{sqrt(\pgfplotspointmeta*\pgfplotspointmeta)}\pgfmathprintnumber{\pgfmathresult}
    },
] table [x expr=\coordindex, y index=0, meta index=0] {datatable.csv};

% The connecting line. Uses a bit of magic to typeset the ranges
\addplot [const plot, black] table [x expr=\coordindex, y expr=0] {datatable.csv};

\tikzstyle{upper node}=[above, font=\tiny, text width=\barwidthval, align=center, inner xsep=0, inner ysep=1pt,]
\tikzstyle{lower node}=[below,]

\node [upper node] at (axis cs: 0,16) {Best\\ case};
\node [upper node] at (axis cs: 1,16) {Oh no};
\node [lower node] at (axis cs: 1,12) {12};
\node [upper node] at (axis cs: 2,12) {Not again};
\node [lower node] at (axis cs: 2,5) {5};
\node [upper node] at (axis cs: 3,5) {Worst\\ case};

\end{axis}
\end{tikzpicture}
\end{document}
  • lualatex acceptable? – JPi Dec 7 '15 at 20:18
  • @JPi preferable pdftex, but any solution is welcome – maetra Dec 7 '15 at 20:54
  • abs (instead of pgfmathabs) seems to work fine. – jak123 Dec 15 '15 at 11:56
  • @jak123 Thanks for the hint, do you know the reason why that is the case? – maetra Dec 15 '15 at 14:56
20
+50

This is a version without pgfplots, but rather with TikZ.

Code

\documentclass[tikz, border=2mm]{standalone}
\usetikzlibrary{calc}
\usepackage{xifthen}

\begin{document}


\newcommand{\Waterfall}[7]%
% bar width,
% bar sep,
% bar color,
%
% max height,
% total
% description,
%
% parts/descriptions
%
{   \colorlet{BarColor}{#3}
    \pgfmathsetmacro{\SF}{#4/#5}% scale factor
    \fill[BarColor] (0,0) rectangle node[black] {#5} ++(#1,#4) coordinate (temp);
    \node[above] at ($(temp)+(-#1/2,0)$) {\tiny#6};
    \xdef\Rest{#5}
    \foreach \drop/\description in {#7}
    {   \pgfmathtruncatemacro{\NewRest}{\Rest-\drop}
        \draw (temp) -- ++ (#2,0) coordinate (temp);
        \node[above] at ($(temp)+(#1/2,0)$) {\tiny\description};
        \fill[BarColor!90] (temp) rectangle node[black] {\drop} ++(#1,-\SF*\drop) coordinate (temp);
        \ifthenelse{\NewRest = 0}
            {}
            {   %\node[below] at ($(temp)+(-#1/2,0)$) {\NewRest};
                \fill[BarColor!90!black] (temp) rectangle node[black] {\NewRest} ++ (-#1,-\SF*\NewRest);
            }
        \xdef\Rest{\NewRest}
    }
    \draw (temp) -- (0,0);
}

\begin{tikzpicture}
    \Waterfall{0.7}{1.5}{orange!50}%
        {5}{16}{Best case}%
        {4/Oh no,7/Not again,5/Worst case}%
\end{tikzpicture}

\end{document}

Output

enter image description here


Edit 1: Now with itermittent rest bars and conditionaly skipped labels.

Code

\documentclass[tikz, border=2mm]{standalone}
\usetikzlibrary{calc}
\usepackage{xifthen}

\begin{document}

\newcommand{\Waterfall}[7]%
% bar width,
% bar sep,
% bar color,
% max height,
%
% total
% description,
%
% parts/descriptions/rest label
%
{   \colorlet{BarColor}{#3}
    \pgfmathsetmacro{\SF}{#4/#5}% scale factor
    \fill[BarColor] (0,0) rectangle node[black] {#5} ++(#1,#4) coordinate (temp);
    \node[above] at ($(temp)+(-#1/2,0)$) {\tiny#6};
    \xdef\Rest{#5}
    \foreach \drop/\description/\restlabel in {#7}
    {   \pgfmathtruncatemacro{\NewRest}{\Rest-\drop}
        \draw (temp) -- ++ (#2,0) coordinate (temp);
        \node[above] at ($(temp)+(#1/2,0)$) {\tiny\description};
        \ifthenelse{\NewRest = 0}
        {   \fill[BarColor] (temp) rectangle node[black] {\drop} ++(#1,-\SF*\drop) coordinate (temp);
        }
        {   \fill[BarColor!50] (temp) rectangle node[black] {\drop} ++(#1,-\SF*\drop) coordinate (temp);
        }
        \ifthenelse{\equal{\restlabel}{}}
        {   \ifthenelse{\NewRest = 0}
            {}
            {   \node[below] at ($(temp)+(-#1/2,0)$) {\NewRest};
                %\fill[BarColor!90!black] (temp) rectangle node[black] {\NewRest} ++ (-#1,-\SF*\NewRest);
            }
        }
        {   \draw (temp) -- ++ (#2,0) ++ (#1,0) coordinate (temp);
            \fill[BarColor] (temp) rectangle node[black] {\NewRest} ++ (-#1,-\SF*\NewRest);
            \node[above] at ($(temp)+(-#1/2,0)$) {\tiny\restlabel};
        }       
        \xdef\Rest{\NewRest}
    }
    \draw (temp) -- (0,0);
}

\begin{tikzpicture}
    \Waterfall{0.7}{1.5}{orange!50}{5}%
        {16}{Best case}%
        {4/Oh no/Expected case,7/Not again/,5/Worst case/}
\end{tikzpicture}

\begin{tikzpicture}
    \Waterfall{0.5}{0.5}{cyan}{8}%
        {21}{good}%
        {6/Oh no/,5/Oh no/worse,4/Oh no/worse,3/Oh no/,2/Oh no/,1/worst/}
\end{tikzpicture}

\end{document}

Output

enter image description here

enter image description here


Edit 2: A workaround to remove the label before the last fall is to use IGNORE as the second to last rest label:

Code

\documentclass[tikz, border=2mm]{standalone}
\usetikzlibrary{calc}
\usepackage{xifthen}

\begin{document}

\newcommand{\Waterfall}[7]%
% bar width,
% bar sep,
% bar color,
% max height,
%
% total
% description,
%
% parts/descriptions/rest label
%
{   \colorlet{BarColor}{#3}
    \pgfmathsetmacro{\SF}{#4/#5}% scale factor
    \fill[BarColor] (0,0) rectangle node[black] {#5} ++(#1,#4) coordinate (temp);
    \node[above] at ($(temp)+(-#1/2,0)$) {\tiny#6};
    \xdef\Rest{#5}
    \foreach \drop/\description/\restlabel in {#7}
    {   \pgfmathtruncatemacro{\NewRest}{\Rest-\drop}
        \draw (temp) -- ++ (#2,0) coordinate (temp);
        \node[above] at ($(temp)+(#1/2,0)$) {\tiny\description};
        \ifthenelse{\NewRest = 0}
        {   \fill[BarColor] (temp) rectangle node[black] {\drop} ++(#1,-\SF*\drop) coordinate (temp);
        }
        {   \fill[BarColor!50] (temp) rectangle node[black] {\drop} ++(#1,-\SF*\drop) coordinate (temp);
        }
        \ifthenelse{\equal{\restlabel}{}}
        {   \ifthenelse{\NewRest = 0}
            {}
            {   \node[below] at ($(temp)+(-#1/2,0)$) {\NewRest};
                %\fill[BarColor!90!black] (temp) rectangle node[black] {\NewRest} ++ (-#1,-\SF*\NewRest);
            }
        }
        {   \ifthenelse{\equal{\restlabel}{IGNORE}}
            {}
            {   \draw (temp) -- ++ (#2,0) ++ (#1,0) coordinate (temp);
                \fill[BarColor] (temp) rectangle node[black] {\NewRest} ++ (-#1,-\SF*\NewRest);
                \node[above] at ($(temp)+(-#1/2,0)$) {\tiny\restlabel};
            }
        }       
        \xdef\Rest{\NewRest}
    }
    \draw (temp) -- (0,0);
}

\begin{tikzpicture}
    \Waterfall{0.7}{1.5}{orange!50}{5}%
        {16}{Best case}%
        {4/Oh no/Expected case,7/Not again/IGNORE,5/Worst case/}
\end{tikzpicture}

\begin{tikzpicture}
    \Waterfall{0.5}{0.5}{cyan}{8}%
        {45}{good}%
        {10/Oh no/,9/Oh no/worse,8/Oh no/worse,7/Oh no/,6/Oh no/IGNORE,5/worst/}
\end{tikzpicture}

\end{document}

Output

enter image description here

enter image description here


Edit 2: Now with pgfkeys to supply key-value options, which makes customization easier:

Code

\documentclass[tikz, border=2mm]{standalone}
\usetikzlibrary{calc}
\usepackage{xifthen}

\tikzset{
    waterfall/.is family,
    waterfall,
    bar width/.initial=0.7,
    bar sep/.initial=0.7,
    total height/.initial=5,
    bar color/.initial=blue!50!cyan,
    drop color/.initial=blue!50!cyan!50,
    draw color/.initial=transparent,
    label options/.style={font=\tiny},
    bar label options/.style={text=black},
    rest label options/.style={text=black},
    total/.initial=20,
    total label/.initial=Total,
}

\newcommand{\WFKey}[1] % access a specific key by name
{\pgfkeysvalueof{/tikz/waterfall/#1}}

\newcommand{\Waterfall}[2][]%
% [options], parts/descriptions/rest label
{   \tikzset{waterfall,#1} % Process Keys passed to command
    \pgfmathsetmacro{\SF}{\WFKey{total height}/\WFKey{total}}% scale factor
    \fill[\WFKey{bar color}] (0,0) rectangle node[waterfall/bar label options] {\WFKey{total}} ++(\WFKey{bar width},\WFKey{total height}) coordinate (temp);
    \node[above, waterfall/label options] at ($(temp)+(-\WFKey{bar width}/2,0)$) {\WFKey{total label}};
    \xdef\Rest{\WFKey{total}}
    \foreach \drop/\description/\restlabel in {#2}
    {   \pgfmathtruncatemacro{\NewRest}{\Rest-\drop}
        \draw (temp) -- ++ (\WFKey{bar sep},0) coordinate (temp);
        \node[above, waterfall/label options] at ($(temp)+(\WFKey{bar width}/2,0)$) {\description};
        \ifthenelse{\NewRest = 0}
        {   \fill[\WFKey{bar color}] (temp) rectangle node[waterfall/bar label options] {\drop} ++(\WFKey{bar width},-\SF*\drop) coordinate (temp);
        }
        {   \fill[\WFKey{drop color}] (temp) rectangle node[waterfall/bar label options] {\drop} ++(\WFKey{bar width},-\SF*\drop) coordinate (temp);
        }
        \ifthenelse{\equal{\restlabel}{}}
        {   \ifthenelse{\NewRest = 0}
            {}
            {   \node[below, waterfall/rest label options] at ($(temp)+(-\WFKey{bar width}/2,0)$) {\NewRest};
                %\fill[BarColor!90!black] (temp) rectangle node[black] {\NewRest} ++ (-#1,-\SF*\NewRest);
            }
        }
        {   \ifthenelse{\equal{\restlabel}{IGNORE}}
            {}
            {   \draw (temp) -- ++ (\WFKey{bar sep},0) ++ (\WFKey{bar width},0) coordinate (temp);
                \fill[\WFKey{bar color}] (temp) rectangle node[waterfall/bar label options] {\NewRest} ++ (-\WFKey{bar width},-\SF*\NewRest);
                \node[above, waterfall/label options] at ($(temp)+(-\WFKey{bar width}/2,0)$) {\restlabel};
            }
        }       
        \xdef\Rest{\NewRest}
    }
    \draw (temp) -- (0,0);
}

\begin{document}

\begin{tikzpicture}
    \Waterfall[total=16, total label=Best case, bar sep=1.4]%
        {4/Oh no/Expected case,7/Not again/IGNORE,5/Worst case/}
\end{tikzpicture}

\begin{tikzpicture}
    \Waterfall%
    [   total=45,
        total label=Good,
        bar color=green!50!gray,
        drop color=gray,
        label options/.style={font=\tiny\sffamily, draw=red, rounded corners=2pt, text=black, inner sep=2pt, above=1mm, minimum height=4mm},
        bar label options/.style={font=\tiny\sffamily, circle, fill= white, text=black, inner sep=2pt},
        rest label options/.style={font=\tiny\sffamily, circle, draw=gray, fill=gray!50, text=black, inner sep=2pt, below=-2mm},
    ]%
        {10/Oh no/,9/Oh no/bad,8/Oh no/worse,7/Oh no/,6/Oh no/IGNORE,5/worst/}
\end{tikzpicture}

\end{document}

Output

enter image description here

enter image description here

  • Thanks for your help. I edited the question to better explain the desired outcome, I hope everything is clear now, if not please let me know. – maetra Dec 15 '15 at 15:19
  • @maetra: Do you still want the label below the steps, e.g. the "12" below the "4" step? – Tom Bombadil Dec 15 '15 at 15:29
  • Well, it would be best if they are omitted in front of full bar, but displayed when another step follows. I can add another picture if you want me too. – maetra Dec 15 '15 at 15:39
  • Would be possible to omit the last 1 before the last full bar in the blue example as well? – maetra Dec 15 '15 at 16:51
  • @maetra: Not the most elegant way probably, but you can get rid of it now by using IGNORE as the second to last rest label. – Tom Bombadil Dec 15 '15 at 17:22
5

EDITED to handle both type of graphs. EDITED to provide different color on the falling bars.

Uses the rather simple syntax:

\wfstart[height of bar]{integer value of initial bar}{label}

to get the graph started, and followed up with successive calls of

\wfall[suppress residual]{integer value of fall}{label}

decrements the bar. Any optional argument will supress the undertext of the residual count from being printed. Also, if the residual value following the fall is zero, the residual-count undertext is also suppressed. Alternate color is used for \wfall. Finally, an invocation of

\wfull{label}

provides for the current full bar value.

Various parameters can be set: \wfbarwd is the width dimension of the bar, \wfskipwd is the gap dimension between bars, \wfrulewd is the thickness of the thin black line, \wfcolor is the rising bar color, \wfcolordn is the falling bar color, and \wfdefaultht is the default height of the starting bar (overridden with optional argument to \wfstart).

\documentclass{article}
\usepackage{xcolor,stackengine}
\newcount\wfbarhtcnt
\newcount\tmpcnt
\newdimen\tmpdim
\newdimen\wfbarht
\newlength\wfbarwd
\newlength\wfskipwd
\newlength\wfrulewd
\wfbarwd=4ex
\wfskipwd=5ex
\wfrulewd=.3pt
\def\wfcolor{orange}
\def\wfcolordn{orange!50}
\def\wfdefaultht{1in}
\newcommand\wfstart[3][\wfdefaultht]{%
  \fboxsep=0pt%
  \wfbarht=#1\relax%
  \gdef\wfbarval{#2}%
  \stackon[0pt]{\rule{\wfbarwd}{\wfrulewd}}{%
    \stackengine{2pt}{\wfrule{#1}{#2}}{\tiny\strut#3}{O}{c}{F}{T}{S}%
  }%
  \ignorespaces%
}
\newcommand\wfall[3][\relax]{%
  \stackon[\dimexpr\wfbarht-1\wfrulewd]{\rule{\wfskipwd}{\wfrulewd}}{%
    \rule{\wfskipwd}{\wfrulewd}}%
    \edef\wfresidual{\the\numexpr\wfbarval-#2}%
    \ifnum\wfresidual=0\def\wfreslabel{}\tmpdim=\wfbarht\relax\else
      \ifx\relax#1\def\wfreslabel{\wfresidual}\else\def\wfreslabel{}\fi%
      \wfbarhtcnt=\wfbarht %
      \tmpdim = \wfbarval pt\relax %
      \tmpcnt=\tmpdim %
      \divide\wfbarhtcnt by \tmpcnt %
      \tmpdim = \wfbarhtcnt pt\relax %
      \tmpdim =#2\tmpdim %     
    \fi%
    \savestack\tmpbox{\wfrule[dn]{\tmpdim}{#2}}%
    \stackengine{\dimexpr\wfbarht-\ht\strutbox-\tmpdim}{%
    \stackon[\dimexpr\wfbarht-\tmpdim]{\rule{\wfbarwd}{\wfrulewd}}{%
      \stackengine{2pt}{\tmpbox}{\tiny\strut#3}{O}{c}{F}{T}{S}}%
    }{\wfreslabel}{O}{c}{F}{T}{L}%
    \addtolength{\wfbarht}{-\tmpdim}%
    \xdef\wfbarval{\wfresidual}%
    \ignorespaces%
}
\newcommand\wfull[1]{%
  \stackon[\dimexpr\wfbarht-1\wfrulewd]{\rule{\wfskipwd}{\wfrulewd}}{%
    \rule{\wfskipwd}{\wfrulewd}}%
  \stackengine{0pt}{\rule{\wfbarwd}{\wfrulewd}}{%
    \stackengine{2pt}{\wfrule{\wfbarht}{\wfbarval}}{\tiny\strut#1}{O}{c}{F}{T}{S}}%
      {O}{c}{F}{T}{S}%
  \ignorespaces%
}
\newcommand\wfrule[3][]{%
  \colorbox{\csname wfcolor#1\endcsname}{\rule{0pt}{#2}%
    \stackon[-.5\ht\strutbox+.5\dimexpr#2]{\rule{\wfbarwd}{0pt}}{#3}}%
}
\begin{document}
\wfstart[2in]{16}{Best case}
\wfall{2}{Oh no}
\wfall[x]{2}{\Longstack{No residual}}
\wfull{\Longstack{Expected case}}
\wfall{7}{\Longstack{Not again}}
\wfull{\Longstack{Dark Color}}
\wfall{5}{\Longstack{or Light Color}}
\end{document}

enter image description here

In the final instance, I show how to get the dark color by using \wfull or alternately the lighter color with \wfall.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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