0

I have a document like this:

\documentclass{article}

\usepackage{tikz,pgfplots}
\usepackage{pgfplotstable}

\begin{filecontents}{data.csv}
Thing;Weight
Dog 1;50
Dog 2;45
Cat 1;10
Cat 2;10
Cat 3;11
Parrot;3
\end{filecontents}

\begin{document}

\pgfplotstableread[col sep=semicolon]{data.csv}{\table}

\begin{tikzpicture}%
    \begin{axis}[%
        ybar=3mm,%
        xtick=data,%
        xticklabels from table={\table}{Thing},%
        x tick label style={rotate=60},%
        nodes near coords%
    ]%
        \addplot table [x expr=\coordindex,y index=1,col sep=semicolon] {\table};%
    \end{axis}
\end{tikzpicture}

\end{document}

Now, I would like to have the two dog bars in one colour, the three cat bars in a different colour and so on. The bars should be equidistant.

I know, each addplot gets its own colour. I would have to make one plot for each group (animal/thing). But pgfplots uses only the first plot for the xticklabels. I could make one table containing all groups with all y values set to 0 and have nodes near coords only plotted if the y value isn’t 0, and then for each group a seperate table with the actual data. But that is a rather ugly hack, as it plots plots over an effectively invisible plot (with bar shift = 0). Is there an elegant way to group bars using colours (not vertical spaces)?

From all I found here on TSX, for my actual document I tinkered this hack:

\documentclass{article}

\usepackage{tikz,pgfplots}
\usepackage{pgfplotstable}
\usepackage{ifthen}

\pgfplotsset{compat=1.16}

\begin{filecontents}{data_b.csv}
Thing;Weight
Dog 1;0
Dog 2;0
Cat 1;0
Cat 2;0
Cat 3;0
Parrot;0
\end{filecontents}

\begin{filecontents}{data_b_dog.csv}
Thing;Weight
Dog 1;50
Dog 2;45
\end{filecontents}

\begin{filecontents}{data_b_cat.csv}
Thing;Weight
Cat 1;10
Cat 2;10
Cat 3;11
\end{filecontents}

\begin{filecontents}{data_b_parrot.csv}
Thing;Weight
Parrot;3
\end{filecontents}

\begin{document}

\pgfplotstableread[col sep=semicolon]{data_b.csv}{\tableall}
\pgfplotstableread[col sep=semicolon]{data_b_dog.csv}{\tabledog}
\pgfplotstableread[col sep=semicolon]{data_b_cat.csv}{\tablecat}
\pgfplotstableread[col sep=semicolon]{data_b_parrot.csv}{\tableparrot}

\begin{tikzpicture}%
    \begin{axis}[%
        ybar=3mm,%
        xtick=data,%
        xticklabels from table={\tableall}{Thing},%
        x tick label style={rotate=60},%
        nodes near coords={%
            \pgfkeys{%
                /pgf/fpu=true,%
                /pgf/fpu/output format=fixed,%
            }%
            \pgfmathtruncatemacro{\Y}{\pgfplotspointmeta}%
            \ifthenelse{\Y=0}{}{\pgfmathprintnumber{\pgfmathresult}}%
        },%
        every axis plot/.append style={bar shift = 0}%
    ]%
        \addplot[draw=none] table [x expr=\coordindex,y index=1,col sep=semicolon] {\tableall};%
        \addplot[blue,fill] table [x expr=\coordindex,y index=1,col sep=semicolon] {\tabledog};%
        \addplot[green,fill] table [x expr=\coordindex+2,y index=1,col sep=semicolon] {\tablecat};%
        \addplot[red,fill] table [x expr=\coordindex+5,y index=1,col sep=semicolon] {\tableparrot};%
    \end{axis}
\end{tikzpicture}

\end{document}

In this MWE the numbers above the bars are wrong. I suppose that’s because I’m switching to integers. I’m not having that problem in my actual document. I have no clue where that comes from here. Anyway, that second MWE shows what I want to plot. This is the output:

enter image description here

4
  • As you can see there either doesn't seem to be an (easy) answer to your question or your question is not totally clear. I am currently not 100% sure if I am sure what output you expect. Could you edit your question and maybe add a sketch of the expected result? – Stefan Pinnow Mar 11 '20 at 6:27
  • I added a second MWE, that shows what I’m trying to achieve. – ehwlt Mar 11 '20 at 12:38
  • Thank you very much, but it would be even better if you would (additionally) add an image of the result. – Stefan Pinnow Mar 11 '20 at 13:32
  • I’ve added an image of the output of the second MWE. The numbers generated by nodes near coords are wrong as I said. Maybe someone understands what I intended to do by looking at the code and knows how to get it right. This is about getting the bars coloured, anyway. If there is a proper solution for that, without my ifthenelse hack, then the numbers should automatically be correct. – ehwlt Mar 11 '20 at 21:28
0

Here is a way how you could achieve what you want. But be aware that this is not very efficient for large tables, because for each \addplot that uses the filter the whole table needs to be fully processed.

For details please have a look at the comments in the code.

% used PGFPlots v1.16
\begin{filecontents}{data.csv}
Thing;Weight
Dog 1;50
Dog 2;45
Cat 1;10
Cat 2;10
Cat 3;11
Parrot;3
\end{filecontents}
\documentclass[border=5pt]{standalone}
\usepackage{xstring}
\usepackage{pgfplots}
\usepackage{pgfplotstable}
    \pgfplotstableread[col sep=semicolon]{data.csv}{\table}
    \pgfplotsset{
        % filter away stuff in column #1 which starts with string #2 of
        % table \table
        discard if not/.style 2 args={
            % adapted from <https://tex.stackexchange.com/a/66716/95441>
            x filter/.code={
                \pgfplotstablegetelem{\coordindex}{#1}\of{\table}
                \IfBeginWith{\pgfplotsretval}{#2}{}{\def\pgfmathresult{}}
            },
        },
    }
\begin{document}
\begin{tikzpicture}
    \begin{axis}[
        ybar=3mm,
        bar shift=0pt,
        xtick=data,
        xticklabels from table={\table}{Thing},
        x tick label style={rotate=60},
    ]
        % dummy to plot all `xtick`s
        \addplot [forget plot] table [x expr=\coordindex,y expr=0] {\table};

        % cycle through entries
        \pgfplotsinvokeforeach {Dog,Cat,Parrot} {
            \addplot+ [
                nodes near coords,
                % apply style
                discard if not={Thing}{#1},
            ] table [x expr=\coordindex,y index=1] {\table};
        }
    \end{axis}
\end{tikzpicture}
\end{document}

image showing the result of above code

4
  • Thanks a lot. That really looks like what I’m looking for. I didn’t know forget plot. That makes things easier. I think I understand what your codes does. PGF is overwhelmingly complex. Would it be possible to include a list of colours and to connect it to the foreach list, something like \addplot+[\colourindex{#1}]? Regarding efficiency, I think more than about 30–40 bars in one plot would be too much information anyway. Do you think, it could be done easier or more efficient using Lua? – ehwlt Mar 14 '20 at 18:05
  • There are several ways to adapt the bar colors. One would be to create a custom cycle list and use it. When you search for that here (on TeX.SX) you will find several answers which will fit your needs. Good luck. – Stefan Pinnow Mar 14 '20 at 19:49
  • I was only half lucky. I defined to lists, one with colours, one just with fill. Then I wrote cycle multiindex* list={my colors\nextlist my fills\nextlist} inside \pgfplotsset. That ain’t doin’ anythin’. I have to use cycle multiindex* list={my colors\nextlist my fills\nextlist} as one of the options of \begin{axis}[]. ybar as another option overrides cycle multiindex* list={my colors\nextlist my fills\nextlist} from \pgfplotsset. But it works. – ehwlt Mar 15 '20 at 14:30
  • That is, because in the ybar style the bar cycle list cycle list is called and thus overrides your cycle list. Depending on your other needs for your real document there are several options to avoid stating your defined cycle list to the axis options after the ybar call. E.g. if you always want to use your defined cycle list in the ybar plots you could append it to the ybar style by stating it in ybar/.append style={...}. Another to define you own ybar style like my ybar/.style={ybar,...} and of course than call this. – Stefan Pinnow Mar 15 '20 at 19:41

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.